在日常开发中,我们经常使用 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 入口,对外提供 ParseConditionsParseSorting
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);
}

关键点说明:

  1. 分组处理:当 (A && B) || C 这种嵌套逻辑出现时,内部运算符与父级不同,需要用 GroupId 来标记括号分组。
  2. 剥离转换Convert / ConvertChecked 节点是编译器插入的类型转换,需要先剥离才能正确识别成员表达式。
  3. 提取常量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 方案,核心要点包括:

  1. 表达式树遍历:通过 ExpressionVisitor 模式递归处理 Lambda 表达式的每个节点
  2. 元数据映射:使用反射+特性定义实体与数据表的映射关系,缓存后高性能访问
  3. 动态组合PredicateBuilder 实现灵活的条件拼接
  4. 类型安全:编译期检查属性名和类型,运行期自动翻译为 SQL
  5. 性能优化:编译表达式树替代反射,参数缓存减少重复构建

虽然这个迷你 ORM 还不能与 EF Core 相提并论,但它涵盖了 ORM 的核心机制,理解这些原理能帮助你更好地使用和扩展现有的 ORM 框架。

在实际项目中,建议根据团队技术栈选择成熟的 ORM(SqlSugar、FreeSql、EF Core 等),但在某些轻量级场景或需要高度定制化时,这种自建方案的灵活性优势就会显现出来。


本文源码来自 SwitchData 项目的 ExpressionParser 模块,完整实现可参考 SwitchData.Business/Web/Extensions/ExpressionParser.csSwitchData.Common/Db/MetadataCache.cs