在日常开发中,我们经常使用 Entity Framework、Dapper、SqlSugar 等 ORM 框架来操作数据库。这些框架的底层都有一个核心机制:把 Lambda 表达式转换成 SQL 查询语句。本文将带你从零实现一个迷你版的 LINQ-to-SQL 方案,深入理解 C# 表达式树的解析原理。
一、什么是表达式树?
表达式树(Expression Tree)是 System.Linq.Expressions
命名空间下的一种数据结构,它将代码表示为一个对象树。每个节点都是一个表达式节点(Expression),比如常量、成员访问、方法调用、二元运算等。
// 普通 Lambda 表达式
Expression<Func<User, bool>> expr = u => u.Name == "张三" && u.Age > 18;
// 这个 Lambda 会被编译器转换为表达式树:
// BinaryExpression (AndAlso)
// ├── BinaryExpression (Equal)
// │ ├── MemberExpression (u.Name)
// │ └── ConstantExpression ("张三")
// └── BinaryExpression (GreaterThan)
// ├── MemberExpression (u.Age)
// └── ConstantExpression (18)
我们的目标就是遍历这棵树,把每个节点翻译成对应的 SQL 片段。
二、整体架构设计
在开始编码前,先规划一下整个方案的架构:
graph TD
A[业务层调用] --> B[ExpressionParser]
B --> C[ConditionVisitor - 遍历表达式树]
B --> D[ParseSorting - 解析排序]
C --> E[List of QueryCondition]
D --> F[OrderedDictionary of sort columns]
E --> G[DataHelper - 构建SQL]
F --> G
G --> H[ADO.NET 执行查询]
I[MetadataCache] --> C
I --> G
核心组件:
| 组件 | 职责 |
|---|---|
ExpressionParser |
入口,对外提供 ParseConditions 和
ParseSorting |
ConditionVisitor |
继承 ExpressionVisitor,遍历表达式树生成条件列表 |
PredicateBuilder |
动态组合多个条件表达式(AND/OR) |
MetadataCache |
通过反射+特性,缓存实体到数据表的映射信息 |
DataHelper |
根据条件列表和排序信息构建 SQL 并执行 |
三、元数据驱动:实体与表的映射
3.1 定义映射特性
我们先定义几个特性类来描述实体类与数据表的映射关系:
// 表特性:标记类对应的数据库表名
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class DbTableAttribute : Attribute
{
public string Name { get; }
public DbObjectType ObjectType { get; }
public DbTableAttribute(string name, DbObjectType objectType = DbObjectType.Table)
{
Name = name.Trim();
ObjectType = objectType;
}
}
// 字段特性:标记属性对应的数据库列名
[AttributeUsage(AttributeTargets.Property)]
public sealed class DbColumnAttribute : Attribute
{
public string Name { get; }
public bool ReadOnly { get; init; } = false;
public DbColumnAttribute(string name)
{
Name = name.Trim();
}
}
// 主键特性
[AttributeUsage(AttributeTargets.Property)]
public sealed class DbPrimaryKeyAttribute : Attribute { }
// 自增特性
[AttributeUsage(AttributeTargets.Property)]
public sealed class DbIdentityAttribute : Attribute { }
3.2 标注实体类
使用特性来描述实体类:
[DbTable("sys_user")]
public class User
{
[DbPrimaryKey]
[DbColumn("user_id")]
public int UserId { get; set; }
[DbColumn("user_name")]
public string Name { get; set; }
[DbColumn("user_age")]
public int Age { get; set; }
[DbColumn("create_time")]
public DateTime CreateTime { get; set; }
}
3.3 元数据缓存
通过反射在首次使用时扫描实体类,缓存映射信息:
public static class MetadataCache
{
// 类型 -> 表元数据 的缓存
private static readonly ConcurrentDictionary<Type, TableMetadata> Cache = new();
public static TableMetadata GetTableMetadata<T>()
{
return GetTableMetadata(typeof(T));
}
public static TableMetadata GetTableMetadata(Type type)
{
return Cache.GetOrAdd(type, CreateMetadata);
}
private static TableMetadata CreateMetadata(Type type)
{
// 读取类上的 DbTableAttribute
var dbTableAttr = type.GetCustomAttribute<DbTableAttribute>();
// 遍历属性,收集带 DbColumnAttribute 的字段
var columns = new List<ColumnMetadata>();
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var colAttr = prop.GetCustomAttribute<DbColumnAttribute>();
if (colAttr == null) continue;
columns.Add(new ColumnMetadata
{
PropertyName = prop.Name,
ColumnName = colAttr.Name,
PropertyType = prop.PropertyType,
Property = prop,
IsPrimaryKey = prop.IsDefined(typeof(DbPrimaryKeyAttribute)),
IsIdentity = prop.IsDefined(typeof(DbIdentityAttribute)),
Getter = CreateGetter(prop),
Setter = CreateSetter(prop),
});
}
// ... 构建 TableMetadata 并返回
}
}
3.4 编译表达式生成高性能访问器
为了避免反射的性能开销,我们用表达式树编译出强类型的 Getter/Setter 委托:
private static Func<object, object> CreateGetter(PropertyInfo property)
{
var instance = Expression.Parameter(typeof(object), "obj");
var cast = Expression.Convert(instance, property.DeclaringType!);
var propertyAccess = Expression.Property(cast, property);
var box = Expression.Convert(propertyAccess, typeof(object));
return Expression.Lambda<Func<object, object>>(box, instance).Compile();
}
private static Action<object, object> CreateSetter(PropertyInfo property)
{
var instance = Expression.Parameter(typeof(object), "obj");
var value = Expression.Parameter(typeof(object), "value");
var instanceCast = Expression.Convert(instance, property.DeclaringType!);
var propertyAccess = Expression.Property(instanceCast, property);
var valueCast = Expression.Convert(value, property.PropertyType);
var assign = Expression.Assign(propertyAccess, valueCast);
return Expression.Lambda<Action<object, object>>(assign, instance, value).Compile();
}
这样生成的委托比反射快 10-100 倍,接近直接访问的性能。
四、核心:ExpressionVisitor 遍历表达式树
4.1 ExpressionVisitor 基础
ExpressionVisitor 是 .NET
内置的抽象基类,它采用访问者模式遍历表达式树的每个节点。我们需要重写关键方法来实现
SQL 翻译。
public class ConditionVisitor : ExpressionVisitor
{
public List<QueryCondition> Conditions { get; } = new();
private readonly TableMetadata _metadata;
private RelationType? _pendingRelation;
private int _currentGroupId;
private int _nextGroupId = 1;
public ConditionVisitor(TableMetadata metadata)
{
_metadata = metadata;
}
}
4.2 处理二元表达式(比较与逻辑运算)
这是最核心的部分,需要处理两种二元表达式:
逻辑运算:&& 和
||
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.AndAlso ||
node.NodeType == ExpressionType.OrElse)
{
var relation = node.NodeType == ExpressionType.AndAlso
? RelationType.AND
: RelationType.OR;
// 递归处理左右子树
var leftNeedGroup = NeedsGroup(node.Left, node.NodeType);
if (leftNeedGroup)
{
var saved = _currentGroupId;
_currentGroupId = _nextGroupId++;
Visit(node.Left);
_currentGroupId = saved;
}
else
{
Visit(node.Left);
}
var prevRelation = _pendingRelation;
_pendingRelation = relation;
var rightNeedGroup = NeedsGroup(node.Right, node.NodeType);
if (rightNeedGroup)
{
var saved = _currentGroupId;
_currentGroupId = _nextGroupId++;
Visit(node.Right);
_currentGroupId = saved;
}
else
{
Visit(node.Right);
}
_pendingRelation = prevRelation;
return node;
}
if (IsComparison(node.NodeType))
{
var left = StripConvert(node.Left);
var right = StripConvert(node.Right);
if (TryExtractMemberAndConstant(left, right,
out var member, out var value, out var memberOnLeft))
{
Conditions.Add(new QueryCondition
{
Column = GetColumnName(member),
Value = value,
Operator = memberOnLeft
? GetOperator(node.NodeType, value)
: GetReversedOperator(node.NodeType, value),
Relation = GetEffectiveRelation(),
GroupId = _currentGroupId
});
}
return node;
}
return base.VisitBinary(node);
}
关键点说明:
- 分组处理:当
(A && B) || C这种嵌套逻辑出现时,内部运算符与父级不同,需要用GroupId来标记括号分组。 - 剥离转换:
Convert/ConvertChecked节点是编译器插入的类型转换,需要先剥离才能正确识别成员表达式。 - 提取常量:
TryExtractMemberAndConstant尝试识别属性 == 常量值的模式,支持左右顺序无关。
4.3 处理成员访问(布尔属性简写)
C# 中 x.IsActive 等价于
x.IsActive == true,我们需要自动处理这种简写:
protected override Expression VisitMember(MemberExpression node)
{
if ((node.Type == typeof(bool) || node.Type == typeof(bool?)) &&
node.Expression is ParameterExpression)
{
Conditions.Add(new QueryCondition
{
Column = GetColumnName(node),
Operator = OperatorType.Equals,
Value = true,
Relation = GetEffectiveRelation(),
GroupId = _currentGroupId
});
return node;
}
return base.VisitMember(node);
}
4.4 处理字符串方法调用
支持
.Contains()、.StartsWith()、.EndsWith()
等字符串方法:
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(string) &&
node.Object is MemberExpression member &&
node.Arguments.Count == 1)
{
var op = node.Method.Name switch
{
"Contains" => OperatorType.Contains,
"StartsWith" => OperatorType.StartsWith,
"EndsWith" => OperatorType.EndsWith,
_ => (OperatorType?)null
};
if (op.HasValue)
{
Conditions.Add(new QueryCondition
{
Column = GetColumnName(member),
Operator = op.Value,
Value = GetConstantValue(node.Arguments[0]),
Relation = GetEffectiveRelation(),
GroupId = _currentGroupId
});
return node;
}
}
return base.VisitMethodCall(node);
}
4.5 解析常量值
常量值的获取比想象中复杂,因为 Lambda 中的变量可能是闭包捕获的:
private static object GetConstantValue(Expression expr)
{
expr = StripConvert(expr);
if (expr is ConstantExpression c) return c.Value;
if (expr is MemberExpression m)
{
if (m.Expression is ConstantExpression container)
{
var obj = container.Value;
if (m.Member is FieldInfo f) return f.GetValue(obj);
if (m.Member is PropertyInfo p) return p.GetValue(obj);
}
if (m.Expression == null)
{
if (m.Member is FieldInfo sf) return sf.GetValue(null);
if (m.Member is PropertyInfo sp) return sp.GetValue(null);
}
}
return Expression.Lambda(expr).Compile().DynamicInvoke();
}
五、解析排序表达式
排序表达式的解析思路与条件解析不同,它通过模拟执行 LINQ 的排序方法链来获取排序列:
public static OrderedDictionary<string, string> ParseSorting<T>(
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy)
{
var dummy = Enumerable.Empty<T>().AsQueryable();
var ordered = orderBy(dummy);
var result = new OrderedDictionary<string, string>();
var expr = ordered.Expression;
while (expr is MethodCallExpression call)
{
var methodName = call.Method.Name;
if (!new[] { "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending" }
.Contains(methodName))
break;
var lambda = (LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand;
var column = GetColumnName(lambda.Body);
var direction = methodName.EndsWith("Descending") ? "DESC" : "ASC";
if (!result.ContainsKey(column))
result[column] = direction;
expr = call.Arguments[0];
}
return new OrderedDictionary<string, string>(result.Reverse());
}
六、动态条件组合:PredicateBuilder
在实际业务中,经常需要根据用户输入动态添加条件。PredicateBuilder
提供了一种优雅的方式:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() => f => true;
public static Expression<Func<T, bool>> False<T>() => f => false;
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var parameter = left.Parameters[0];
var rightBody = ReplaceParameter(right.Body, right.Parameters[0], parameter);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left.Body, rightBody), parameter);
}
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var parameter = left.Parameters[0];
var rightBody = ReplaceParameter(right.Body, right.Parameters[0], parameter);
return Expression.Lambda<Func<T, bool>>(
Expression.OrElse(left.Body, rightBody), parameter);
}
private static Expression ReplaceParameter(Expression body,
ParameterExpression oldParam, ParameterExpression newParam)
{
return new ParameterReplacerVisitor(oldParam, newParam).Visit(body);
}
private class ParameterReplacerVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParam;
private readonly ParameterExpression _newParam;
public ParameterReplacerVisitor(ParameterExpression oldParam,
ParameterExpression newParam)
{
_oldParam = oldParam;
_newParam = newParam;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == _oldParam ? _newParam : base.VisitParameter(node);
}
}
}
使用示例:
// 动态构建查询条件
var predicate = PredicateBuilder.True<User>();
if (!string.IsNullOrEmpty(name))
predicate = predicate.And(u => u.Name.Contains(name));
if (age.HasValue)
predicate = predicate.And(u => u.Age >= age.Value);
if (onlyActive)
predicate = predicate.And(u => u.IsActive);
var conditions = ExpressionParser.ParseConditions(predicate);
七、整合:从表达式到 SQL 的完整流程
让我们看一下 BaseService
是如何把这些组件串联起来的:
public static async Task<(DataTable Table, int Total)> BaseQueryAsync<T>(
Expression<Func<T, bool>> where,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy,
int offset, int limit,
CancellationToken cancellationToken = default)
{
var metadata = MetadataCache.GetTableMetadata<T>();
var conditions = ExpressionParser.ParseConditions(where);
var sortColumns = orderBy != null
? ExpressionParser.ParseSorting(orderBy)
: new OrderedDictionary<string, string>();
var result = await DataHelper.QueryDataAsync(
_dbHelper, offset, limit,
metadata.DbTableName, conditions, sortColumns,
cancellationToken);
return (result.DataTable, result.Total);
}
调用示例:
var (table, total) = await BaseService.BaseQueryAsync<User>(
where: u => u.Name.Contains("张") && u.Age > 18,
orderBy: q => q.OrderByDescending(u => u.CreateTime),
offset: 0, limit: 20);
对应的 SQL 大致为:
SELECT * FROM sys_user
WHERE user_name LIKE '%张%' AND user_age > @p0
ORDER BY create_time DESC
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
八、性能考量与优化
8.1 元数据缓存
ConcurrentDictionary<Type, TableMetadata>
保证线程安全,GetOrAdd
实现懒加载。元数据只在首次访问时构建一次。
8.2 编译委托
使用 Expression.Lambda().Compile() 生成的 Getter/Setter
委托比反射快得多:
方式 耗时(百万次调用)
直接属性访问 ~50ms
编译表达式树委托 ~80ms
反射 PropertyInfo ~800ms
8.3 参数缓存
CachingMechanism 缓存
IDataParameter[],避免每次命令都重新构建参数对象。
8.4 SQL 注入防护
所有值都通过参数化查询传递,从根本上防止 SQL 注入。
九、扩展与改进方向
- 支持
In集合包含操作符 - 支持日期范围比较
- 支持 Include(预加载)导航属性自动 JOIN
- 多数据库支持(MySQL、PostgreSQL)
- 异步流式查询(IAsyncEnumerable)
十、总结
本文实现的迷你 ORM 展示了一个完整的 LINQ-to-SQL 方案,核心要点包括:
- 表达式树遍历:通过
ExpressionVisitor模式递归处理 Lambda 表达式的每个节点 - 元数据映射:使用反射+特性定义实体与数据表的映射关系,缓存后高性能访问
- 动态组合:
PredicateBuilder实现灵活的条件拼接 - 类型安全:编译期检查属性名和类型,运行期自动翻译为 SQL
- 性能优化:编译表达式树替代反射,参数缓存减少重复构建
虽然这个迷你 ORM 还不能与 EF Core 相提并论,但它涵盖了 ORM 的核心机制,理解这些原理能帮助你更好地使用和扩展现有的 ORM 框架。
在实际项目中,建议根据团队技术栈选择成熟的 ORM(SqlSugar、FreeSql、EF Core 等),但在某些轻量级场景或需要高度定制化时,这种自建方案的灵活性优势就会显现出来。
本文源码来自 SwitchData 项目的 ExpressionParser 模块,完整实现可参考
SwitchData.Business/Web/Extensions/ExpressionParser.cs和SwitchData.Common/Db/MetadataCache.cs。