首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# SQL 查詢物件化:建構型別安全的動態查詢建構器

     編輯:關於C語言

動機

字串拼湊 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 values) : base(column, values.ToArray()) { }
    protected override string OperatorSymbol => "IN";
    public override string GetFilter(IDriverType driver)
    {
        var arr = (Array)Value;
        var names = new string[arr.Length];
        for (int i = 0; i < arr.Length; i++)
            names[i] = driver.FormatParameterName(ColumnName, Sequence + i);
        return $"{ColumnName} IN ({string.Join(",", names)})";
    }
    public override IDataParameter[] GetParameters(IDriverType driver)
    {
        var arr = (Array)Value;
        var ps = new IDataParameter[arr.Length];
        for (int i = 0; i < arr.Length; i++)
            ps[i] = driver.CreateParameter(driver.FormatParameterName(ColumnName, Sequence + i), arr.GetValue(i));
        return ps;
    }
}


查詢建構器


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));
    }
}

SQL Server 驅動適配器


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()) { /* ... */ }
}

進階擴充方向

  • Join 支援: 引入 JoinExpression、JoinType (Inner/Left/Right)。
  • GroupBy/Having: 擴充 QueryBuilder 屬性與 Build 邏輯。
  • 子查詢: SubQueryExpression 遞迴建構。
  • Dapper/Entity Framework 整合: 輸出 CommandDefinition 或 IQueryable。
  • 原始碼產生器: 根據資料庫 Schema 產生強型別 Table/Column 常數類別,消除魔術字串。

現有成熟庫參考

  • SqlKata:優雅的查詢建構器,支援多種資料庫方言。
  • Dapper + Dapper.QueryBuilder:輕量級擴充。
  • Entity Framework Core:LINQ 即型別安全查詢,自動參數化。
  • LINQ to DB:LINQ 語法直接翻譯 SQL,零追蹤開銷。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved