C# 类型系统上实现一个 SQL 查询引擎原型
作者:码云数智-园园
在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)五、关键设计亮点
| 特性 | 实现方式 | 优势 |
|---|---|---|
| 类型安全 | 泛型 + 表达式树 | 编译时检查字段名、类型 |
| 链式 API | Fluent Builder | 符合 LINQ 习惯 |
| SQL 生成 | 表达式树遍历 | 避免字符串拼接错误 |
| 投影支持 | MemberInitExpression 解析 | 支持匿名对象选择 |
六、可扩展方向
- 参数化查询
- 在
FormatValue中改为返回参数占位符(如@p0),并收集参数值
- 在
- JOIN 支持
- 引入
Join<TOuter, TInner, TResult>方法,解析 lambda 中的关联条件
- 引入
- 更多运算符
- 支持
Contains(→IN)、StartsWith(→LIKE)
- 支持
- 方言适配
- 抽象
ISqlDialect,支持 MySQL/PostgreSQL/SQLite 不同语法
- 抽象
- 执行层
- 添加
.ToList(),用DbCommand执行 SQL 并反序列化结果
- 添加
七、为什么这很“C#”?
- 表达式树(Expression Trees) 是 C# 独有的强大特性,允许在运行时分析代码结构。
- 泛型 + 匿名类型 让投影类型安全且简洁。
- LINQ 语法糖 使查询像写 SQL 一样自然,但由编译器保证正确性。
💡 这正是 Entity Framework、Dapper.Linq 等 ORM 的底层思想!
总结
我们用 不到 200 行核心代码,在 C# 类型系统上构建了一个:
- ✅ 类型安全
- ✅ 可组合
- ✅ 可翻译为 SQL
- ✅ 支持基本查询操作
的 SQL 查询引擎原型。它展示了 如何将语言特性(表达式树)与领域问题(SQL 生成)优雅结合——这正是现代 C# 高级库的设计精髓。
到此这篇关于C# 类型系统上实现一个 SQL 查询引擎原型的文章就介绍到这了,更多相关C# SQL 查询引擎内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
