字串拼湊 SQL 易錯、難維護、有注入風險。將查詢條件**物件化**,以流暢介面 (Fluent API) 建構 WHERE 子句,再由建構器產生參數化 SQL,兼顧型別安全、可讀性與效能。
public interface IExpression
{
/// 產生 WHERE 片段 (含參數佔位符)
string GetFilter(IDriverType driver);
/// 取得對應參數陣列
IDataParameter[] GetParameters(IDriverType driver);
/// 組合子表達式 (AND/OR)
void Add(LogicOperator op, params IExpression[] expressions);
void Add(params IExpression[] expressions); // 預設 AND
}
public enum LogicOperator { And, Or }
public interface IDriverType
{
string FormatParameterName(string baseName, string sequence); // e.g. @Name_0
IDataParameter CreateParameter(string name, object value);
}
[Serializable]
public abstract class ExpressionBase : IExpression
{
private readonly List _children = new();
private readonly List _operators = new(); // 與 _children 對應,第一個通常忽略
public string Sequence { get; set; } = "";
public void Add(LogicOperator op, params IExpression[] expressions)
{
foreach (var exp in expressions ?? Array.Empty())
{
if (exp == null) continue;
exp.Sequence = Sequence + _children.Count;
_operators.Add(op);
_children.Add(exp);
}
}
public void Add(params IExpression[] expressions) => Add(LogicOperator.And, expressions);
public virtual string GetFilter(IDriverType driver)
{
if (_children.Count == 0) return "1=1";
var sb = new StringBuilder();
for (int i = 0; i < _children.Count; i++)
{
string op = i == 0 ? "" : (_operators[i] == LogicOperator.And ? " AND " : " OR ");
sb.Append(op).Append('(').Append(_children[i].GetFilter(driver)).Append(')');
}
return sb.ToString();
}
public virtual IDataParameter[] GetParameters(IDriverType driver)
{
var list = new List();
foreach (var child in _children)
{
var ps = child.GetParameters(driver);
if (ps != null) list.AddRange(ps);
}
return list.ToArray();
}
}
public abstract class CompareExpression : ExpressionBase
{
public string ColumnName { get; set; }
public object Value { get; set; }
protected CompareExpression(string column, object value)
{
ColumnName = column;
Value = value;
}
protected abstract string OperatorSymbol { get; } // =, <>, >, <, >=, <=, LIKE, IN, NOT IN
public override string GetFilter(IDriverType driver)
{
string paramName = driver.FormatParameterName(ColumnName, Sequence);
return $"{ColumnName} {OperatorSymbol} {paramName}";
}
public override IDataParameter[] GetParameters(IDriverType driver)
{
return new[] { driver.CreateParameter(driver.FormatParameterName(ColumnName, Sequence), Value) };
}
}
// 具體比較類別
public sealed class EqualExpression : CompareExpression
{
public EqualExpression(string column, object value) : base(column, value) { }
protected override string OperatorSymbol => "=";
}
public sealed class NotEqualExpression : CompareExpression { /* <> */ }
public sealed class GreaterThanExpression : CompareExpression { /* > */ }
public sealed class LessThanExpression : CompareExpression { /* < */ }
public sealed class LikeExpression : CompareExpression { /* LIKE */ }
public sealed class InExpression : CompareExpression
{
public InExpression(string column, IEnumerable
public class QueryBuilder
{
private readonly string _tableName;
private readonly List _columns = new() { "*" };
private IExpression _where = new ExpressionBase(); // 空條件
private string _orderBy = "";
private int? _top = null;
public QueryBuilder(string tableName) => _tableName = tableName;
public QueryBuilder Select(params string[] columns) { _columns.Clear(); _columns.AddRange(columns); return this; }
public QueryBuilder Where(IExpression expr) { _where = expr; return this; }
public QueryBuilder OrderBy(string orderBy) { _orderBy = orderBy; return this; }
public QueryBuilder Top(int count) { _top = count; return this; }
public (string Sql, IDataParameter[] Parameters) Build(IDriverType driver)
{
var sb = new StringBuilder();
sb.Append("SELECT ");
if (_top.HasValue) sb.Append($"TOP {_top.Value} ");
sb.Append(string.Join(", ", _columns));
sb.Append(" FROM ").Append(_tableName);
string whereSql = _where.GetFilter(driver);
if (whereSql != "1=1") sb.Append(" WHERE ").Append(whereSql);
if (!string.IsNullOrEmpty(_orderBy)) sb.Append(" ORDER BY ").Append(_orderBy);
return (sb.ToString(), _where.GetParameters(driver));
}
}
public class SqlServerDriver : IDriverType
{
private int _paramIndex = 0;
public string FormatParameterName(string baseName, string sequence)
=> $"@{baseName}_{sequence}_{Interlocked.Increment(ref _paramIndex)}";
public IDataParameter CreateParameter(string name, object value)
=> new SqlParameter(name, value ?? DBNull.Value);
}
var driver = new SqlServerDriver();
var query = new QueryBuilder("Orders")
.Select("OrderID", "CustomerID", "OrderDate", "TotalAmount")
.Where(new ExpressionBase()
.Add(new GreaterThanExpression("OrderDate", new DateTime(2024,1,1)))
.Add(LogicOperator.And, new LessThanExpression("OrderDate", new DateTime(2024,12,31)))
.Add(LogicOperator.Or, new InExpression("CustomerID", new[] { "VIP001", "VIP002" }))
)
.OrderBy("OrderDate DESC")
.Top(100)
.Build(driver);
Console.WriteLine(query.Sql);
/*
SELECT TOP 100 OrderID, CustomerID, OrderDate, TotalAmount
FROM Orders
WHERE (OrderDate > @OrderDate_0_1) AND (OrderDate < @OrderDate_0_2) OR (CustomerID IN (@CustomerID_0_3,@CustomerID_0_4))
ORDER BY OrderDate DESC
*/
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(query.Sql, conn))
{
cmd.Parameters.AddRange(query.Parameters);
conn.Open();
using (var reader = cmd.ExecuteReader()) { /* ... */ }
}
JoinExpression、JoinType (Inner/Left/Right)。QueryBuilder 屬性與 Build 邏輯。SubQueryExpression 遞迴建構。CommandDefinition 或 IQueryable。Table/Column 常數類別,消除魔術字串。