C# 类型系统上实现一个 SQL 查询引擎原型

 更新时间:2026年03月07日 09:19:34   作者:码云数智-园园  
在C#类型系统上实现一个SQL查询引擎,核心目标是利用 C# 的强类型、泛型、表达式树(Expression Trees)等特性,构建一个类型安全、可组合、可编译为 SQL 的查询系统,本文介绍C# 类型系统上实现一个 SQL 查询引擎,感兴趣的朋友一起看看吧

在 C# 类型系统上实现一个 SQL 查询引擎,核心目标是:利用 C# 的强类型、泛型、表达式树(Expression Trees)等特性,构建一个类型安全、可组合、可编译为 SQL 的查询系统。这类似于 Entity Framework Core 或 LINQ to SQL 的核心机制。

下面我们将从零开始,逐步设计并实现一个简化但功能完整的 类型化 SQL 查询引擎原型

一、目标与范围

我们要实现的功能:

  • 支持 SELECT, WHERE, ORDER BY, LIMIT
  • 支持基本比较(==, >, <)、逻辑运算(&&, ||
  • 支持投影(Select(x => new { x.Id, x.Name })
  • 将 LINQ 表达式树翻译为 SQL 字符串
  • 全程类型安全:编译时报错,而非运行时

不实现(简化):

  • JOIN、GROUP BY、子查询、聚合函数
  • 参数化防注入(但会预留接口)
  • 复杂类型映射(仅支持简单 POCO)

二、整体架构设计

// 1. 实体类(用户定义)
public class User 
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
// 2. 查询入口:DbSet<T>
var users = new DbSet<User>("users");
// 3. LINQ 风格链式调用
var query = users
    .Where(u => u.Age > 18)
    .OrderBy(u => u.Name)
    .Select(u => new { u.Id, u.Name })
    .Take(10);
// 4. 生成 SQL
string sql = query.ToSql();
// 输出: SELECT Id, Name FROM users WHERE Age > 18 ORDER BY Name LIMIT 10

三、核心组件实现

1.DbSet<T>:查询起点

public class DbSet<T>
{
    private readonly string _tableName;
    public DbSet(string tableName) => _tableName = tableName;
    public Query<T> Where(Expression<Func<T, bool>> predicate)
        => new Query<T>(_tableName).Where(predicate);
    // 其他方法委托给 Query<T>
}

2.Query<T>:查询构建器(支持链式调用)

public class Query<T>
{
    public string TableName { get; }
    public Expression? WhereClause { get; private set; }
    public LambdaExpression? OrderByClause { get; private set; }
    public LambdaExpression? SelectClause { get; private set; }
    public int? Limit { get; private set; }
    internal Query(string tableName) => TableName = tableName;
    public Query<T> Where(Expression<Func<T, bool>> predicate)
    {
        WhereClause = predicate;
        return this;
    }
    public Query<T> OrderBy<TKey>(Expression<Func<T, TKey>> keySelector)
    {
        OrderByClause = keySelector;
        return this;
    }
    public Query<TResult> Select<TResult>(Expression<Func<T, TResult>> selector)
    {
        return new Query<TResult>(TableName)
        {
            WhereClause = WhereClause,
            OrderByClause = OrderByClause,
            SelectClause = selector,
            Limit = Limit
        };
    }
    public Query<T> Take(int count)
    {
        Limit = count;
        return this;
    }
    public string ToSql()
    {
        return SqlTranslator.Translate(this);
    }
}

3.SqlTranslator:表达式树 → SQL

这是最核心的部分!我们遍历表达式树,生成 SQL 片段。

public static class SqlTranslator
{
    public static string Translate<T>(Query<T> query)
    {
        var select = BuildSelect(query.SelectClause ?? GetDefaultSelect<T>());
        var from = $"FROM {query.TableName}";
        var where = query.WhereClause != null ? $"WHERE {Visit(query.WhereClause.Body)}" : "";
        var orderBy = query.OrderByClause != null ? $"ORDER BY {GetMemberName(query.OrderByClause.Body)}" : "";
        var limit = query.Limit.HasValue ? $"LIMIT {query.Limit}" : "";
        return $"{select} {from} {where} {orderBy} {limit}".Replace("  ", " ").Trim();
    }
    private static string BuildSelect(LambdaExpression selector)
    {
        if (selector.Body is MemberInitExpression init)
        {
            var members = init.Bindings.OfType<MemberAssignment>()
                .Select(b => b.Member.Name);
            return "SELECT " + string.Join(", ", members);
        }
        else if (selector.Body is ParameterExpression)
        {
            // SELECT * 情况
            return "SELECT *";
        }
        else if (selector.Body is MemberExpression member)
        {
            return $"SELECT {member.Member.Name}";
        }
        throw new NotSupportedException("Unsupported select expression");
    }
    private static string Visit(Expression expr)
    {
        return expr switch
        {
            BinaryExpression bin => VisitBinary(bin),
            MemberExpression mem => mem.Member.Name,
            ConstantExpression con => FormatValue(con.Value),
            MethodCallExpression call => throw new NotSupportedException("Method calls not supported"),
            _ => throw new NotSupportedException($"Expression {expr.NodeType} not supported")
        };
    }
    private static string VisitBinary(BinaryExpression bin)
    {
        var left = Visit(bin.Left);
        var right = Visit(bin.Right);
        var op = bin.NodeType switch
        {
            ExpressionType.Equal => "=",
            ExpressionType.GreaterThan => ">",
            ExpressionType.LessThan => "<",
            ExpressionType.AndAlso => "AND",
            ExpressionType.OrElse => "OR",
            _ => throw new NotSupportedException($"Operator {bin.NodeType} not supported")
        };
        return $"({left} {op} {right})";
    }
    private static string FormatValue(object? value)
    {
        return value switch
        {
            null => "NULL",
            string s => $"'{s.Replace("'", "''")}'", // 简单转义
            bool b => b ? "1" : "0",
            _ => value.ToString()!
        };
    }
    private static string GetMemberName(Expression expr)
    {
        return expr switch
        {
            MemberExpression m => m.Member.Name,
            UnaryExpression u when u.NodeType == ExpressionType.Convert => GetMemberName(u.Operand),
            _ => throw new ArgumentException("Expected member access")
        };
    }
    private static LambdaExpression GetDefaultSelect<T>()
    {
        var param = Expression.Parameter(typeof(T), "x");
        return Expression.Lambda(param, param);
    }
}

四、使用示例

var users = new DbSet<User>("users");
// 示例 1:完整查询
var query1 = users
    .Where(u => u.Age > 18 && u.Name == "Alice")
    .OrderBy(u => u.Name)
    .Select(u => new { u.Id, u.Name })
    .Take(5);
Console.WriteLine(query1.ToSql());
// 输出: SELECT Id, Name FROM users WHERE ((Age > 18) AND (Name = 'Alice')) ORDER BY Name LIMIT 5
// 示例 2:仅 WHERE
var query2 = users.Where(u => u.Id == 100);
Console.WriteLine(query2.ToSql());
// 输出: SELECT * FROM users WHERE (Id = 100)

五、关键设计亮点

特性实现方式优势
类型安全泛型 + 表达式树编译时检查字段名、类型
链式 APIFluent Builder符合 LINQ 习惯
SQL 生成表达式树遍历避免字符串拼接错误
投影支持MemberInitExpression 解析支持匿名对象选择

六、可扩展方向

  1. 参数化查询
    • FormatValue 中改为返回参数占位符(如 @p0),并收集参数值
  2. JOIN 支持
    • 引入 Join<TOuter, TInner, TResult> 方法,解析 lambda 中的关联条件
  3. 更多运算符
    • 支持 Contains(→ IN)、StartsWith(→ LIKE
  4. 方言适配
    • 抽象 ISqlDialect,支持 MySQL/PostgreSQL/SQLite 不同语法
  5. 执行层
    • 添加 .ToList(),用 DbCommand 执行 SQL 并反序列化结果

七、为什么这很“C#”?

  • 表达式树(Expression Trees) 是 C# 独有的强大特性,允许在运行时分析代码结构。
  • 泛型 + 匿名类型 让投影类型安全且简洁。
  • LINQ 语法糖 使查询像写 SQL 一样自然,但由编译器保证正确性。

💡 这正是 Entity Framework、Dapper.Linq 等 ORM 的底层思想!

总结

我们用 不到 200 行核心代码,在 C# 类型系统上构建了一个:

  • ✅ 类型安全
  • ✅ 可组合
  • ✅ 可翻译为 SQL
  • ✅ 支持基本查询操作

的 SQL 查询引擎原型。它展示了 如何将语言特性(表达式树)与领域问题(SQL 生成)优雅结合——这正是现代 C# 高级库的设计精髓。

到此这篇关于C# 类型系统上实现一个 SQL 查询引擎原型的文章就介绍到这了,更多相关C#  SQL 查询引擎内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#中委托的进一步理解

    C#中委托的进一步理解

    这篇文章主要介绍了C#中委托的进一步理解,本文讲解了委托类型、建立委托链、移除委托链等内容,需要的朋友可以参考下
    2015-02-02
  • C#简单实现显示中文格式星期几的方法

    C#简单实现显示中文格式星期几的方法

    这篇文章主要介绍了C#简单实现显示中文格式星期几的方法,涉及C#常见的日期与时间以及字符串转换等相关操作技巧,需要的朋友可以参考下
    2016-07-07
  • c#处理3种json数据的实例

    c#处理3种json数据的实例

    这篇文章主要介绍了c#处理包含数组、对象的复杂json数据的方法,,需要的朋友可以参考下
    2014-03-03
  • C# 忽略大小写进行字符串比较

    C# 忽略大小写进行字符串比较

    这篇文章主要介绍了C# 字符串比较忽略大小写的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下
    2021-02-02
  • C#通过KD树进行距离最近点的查找

    C#通过KD树进行距离最近点的查找

    这篇文章主要为大家详细介绍了C#通过KD树进行距离最近点的查找,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • C#使用foreach语句遍历集合类型的方法

    C#使用foreach语句遍历集合类型的方法

    这篇文章主要介绍了C#使用foreach语句遍历集合类型的方法,可实现通过foreach语句遍历集合类的功能,需要的朋友可以参考下
    2015-06-06
  • C#获取并修改文件扩展名的方法

    C#获取并修改文件扩展名的方法

    这篇文章主要介绍了C#获取并修改文件扩展名的方法,实例分析了C#编程方式修改文件扩展名的技巧,涉及Path类的使用方法,需要的朋友可以参考下
    2015-04-04
  • C#实现左截取和右截取字符串实例

    C#实现左截取和右截取字符串实例

    这篇文章主要介绍了C#实现左截取和右截取字符串实例,是针对字符串的常用操作,非常具有实用价值,需要的朋友可以参考下
    2014-10-10
  • C#中载入界面的常用方法

    C#中载入界面的常用方法

    这篇文章主要介绍了C#中载入界面的常用方法,涉及窗体的操作,非常具有实用价值,需要的朋友可以参考下
    2014-10-10
  • C# Mqtt 断线重连的实现代码

    C# Mqtt 断线重连的实现代码

    这篇文章主要介绍了C# Mqtt 断线重连,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09

最新评论