You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# 基于Expression Tree实现小型ORM WHERE条件比较时遇未定义变量异常

自定义简易ORMWhere扩展方法的异常排查与修复

问题背景

  • 课程项目需要开发简易ORM,计划参考LINQ to SQL的WHERE子句实现逻辑,从过滤Lambda表达式中提取左侧属性字段、与右侧常量值做相等匹配,不希望从零重复造轮子。
  • 编写自定义ORMWhere扩展方法时,持续抛出如下异常:
System.InvalidOperationException: 'variable 'source' of type 'Project.People' referenced from scope '', but it is not defined

问题复现代码

class People
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    public static List<People> CreatePeople()
    {
        return new List<People>()
        {
            new People { Id = 1, Age = 10, Name = "A"},
            new People { Id = 2, Age = 11, Name = "B"},
            new People { Id = 3, Age = 12, Name = "C"},
            new People { Id = 4, Age = 13, Name = "D"},
            new People { Id = 5, Age = 14, Name = "E"}
        };
    }

    static void Main(string[] args)
    {
        var people = CreatePeople().AsQueryable();
        var test = people.ORMWhere(x => x.Name == "A");
    }
}

public class Extensions
{
    public static IQueryable<TResult> ORMWhere<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> predicate)
    {
        var body = predicate.Body as BinaryExpression;
        var parameeter = Expression.Parameter(typeof(TSource), "source");
        switch (body.NodeType)
        {
            case ExpressionType.Equal:
                var left = body.Left.GetExpressionName();
                var right = body.Right.GetExpressionValue();
                var expr = Expression.Property(parameeter, left);
                var lol = Expression.Equal(
                    expr,
                    Expression.Constant(right)
                );
                var okk = Expression.Lambda(lol).Compile().DynamicInvoke();
                break;
            default:
                throw new Exception();
        }
        return null;
    }
}

注:代码中依赖的GetExpressionName()、GetExpressionValue()为自定义的辅助提取方法,用于从表达式节点提取属性名、常量值。

异常核心成因

  • 直接触发异常的原因:构建Lambda表达式时未正确绑定参数作用域。代码中定义了名称为source的ParameterExpression,并将其作为属性访问节点的参数来源,但在生成最终Lambda时,调用无参重载的Expression.Lambda(lol),该方法默认生成无参数的Lambda委托,没有把parameeter加入Lambda的参数列表,导致编译表达式树时找不到已被引用的source变量定义。
  • 设计层面的配套问题:
    • 扩展方法的泛型签名与预期的Where语义不匹配。正常的过滤方法应该固定返回与输入序列元素类型一致的IQueryable<TSource>,接收的谓语参数必须是返回布尔值的Expression<Func<TSource, bool>>;当前签名额外引入了无意义的TResult泛型,会导致类型推导混乱,最终返回null也完全无法承担查询过滤的职责。
    • 表达式处理逻辑冗余。代码从原谓语表达式中拆解属性、常量值后,没有复用原表达式的参数绑定关系,反而新建了一套独立的表达式节点,完全没有必要。
    • 流程逻辑错误。代码中编译生成表达式后直接调用DynamicInvoke(),既没有把构建好的查询表达式与原IQueryable的提供程序绑定,也没有对接自定义ORM的SQL生成流程,完全偏离了扩展方法的设计目标。

修复方案

  1. 修正扩展方法签名
    将方法调整为与内置Queryable.Where一致的语义,去掉多余的TResult泛型参数:
    public static IQueryable<TSource> ORMWhere<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    
  2. 正确绑定Lambda参数
    构建最终过滤表达式时,必须使用原谓语表达式自带的参数(即用户传入的x => x.Name == "A"里的参数x)生成Lambda,不能凭空新建参数却不加入Lambda的参数列表,示例:
    // 提取原谓语的参数,作为Lambda的作用域参数
    var param = predicate.Parameters[0];
    // 构建过滤表达式体
    var body = Expression.Equal(
        Expression.Property(param, left), 
        Expression.Constant(right)
    );
    // 生成Lambda时把传入的param绑定到参数列表
    var filterLambda = Expression.Lambda<Func<TSource, bool>>(body, param);
    
  3. 对接IQueryable的查询执行流程
    生成合法的过滤Lambda后,通过原查询的Provider创建对应查询,而不是返回null;后续可以在自定义的QueryProvider中遍历表达式树,把“属性名=常量值”的节点转成ORM对应的SQL WHERE子句,实现自己的查询解析逻辑:
    // 调用原提供程序创建带过滤条件的查询
    return source.Provider.CreateQuery<TSource>(
        Expression.Call(
            typeof(Queryable).GetMethod(nameof(Queryable.Where), new[] { typeof(IQueryable<TSource>), typeof(Expression<Func<TSource, bool>>) })
            .MakeGenericMethod(typeof(TSource)),
            source.Expression,
            Expression.Quote(filterLambda)
        )
    );
    
  4. 冗余逻辑裁剪
    对于简单的相等匹配场景,其实不需要重新拆解表达式、重建节点——原用户传入的Lambda本身就是合法的过滤表达式,只有在需要把表达式转成自定义ORM的SQL格式时,才需要在提供程序内部做节点遍历提取,不需要在扩展方法入口处做无意义的重复构建。

内容的提问来源于stack exchange,提问作者Giga Meta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.31 02:57:03