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生成流程,完全偏离了扩展方法的设计目标。
- 扩展方法的泛型签名与预期的
修复方案
- 修正扩展方法签名
将方法调整为与内置Queryable.Where一致的语义,去掉多余的TResult泛型参数:public static IQueryable<TSource> ORMWhere<TSource>( this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) - 正确绑定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); - 对接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) ) ); - 冗余逻辑裁剪
对于简单的相等匹配场景,其实不需要重新拆解表达式、重建节点——原用户传入的Lambda本身就是合法的过滤表达式,只有在需要把表达式转成自定义ORM的SQL格式时,才需要在提供程序内部做节点遍历提取,不需要在扩展方法入口处做无意义的重复构建。
内容的提问来源于stack exchange,提问作者Giga Meta
相关产品推荐
相关产品推荐

