如何将MethodInfo结果赋值给属性以实现Entity Framework动态查询?
问题描述
目标是实现更具动态性和面向对象的Entity Framework数据库查询,编写了如下类:
class Target { [MapFrom(nameof(GetSum))] public int Sum { get; set; } public static Expression<Func<int>> GetSum(Source s) => () => s.First + s.Second; }
希望生成如下形式的表达式之一:
dbContext.Table.Select(x => {Sum = x.First + x.Second }) or dbContext.Table.Select(x => {Sum = GetSum(x) })
尝试构建x => {Sum = GetSum(x)}表达式来为属性赋值,编写了如下方法:
private static MemberBinding _getMethodAssigned( string propertyName, // this is Sum MethodInfo methodInfo, // this is the MethodInfo of GetSum ParameterExpression parameter, // this is the x Type type // this is the type Target ) { MethodCallExpression methodCallExpression = Expression.Call(null, methodInfo, parameter); MemberInfo member = type.GetMember(propertyName).First(); return Expression.Bind(member, methodCallExpression); }
但该方法无法正常工作,因为Sum属性类型为int,而GetSum方法返回类型为Expression<Func<int>>,实际想要的是将GetSum的执行结果赋值给Sum,请问该如何实现?
解决方案
问题核心在于GetSum返回的是Expression<Func<int>>,而非直接的int值,我们需要针对EF查询的场景做不同处理:
方案1:提取表达式内部逻辑(适配EF数据库查询,推荐)
EF需要将表达式解析为SQL,直接执行表达式会导致无法转换,因此最优方式是提取GetSum返回表达式的Body部分,直接用于赋值:
private static MemberBinding _getMethodAssigned( string propertyName, MethodInfo methodInfo, ParameterExpression parameter, Type type ) { // 调用GetSum方法,得到Expression<Func<int>>实例的表达式 var getSumCall = Expression.Call(null, methodInfo, parameter); // 提取该表达式的Body部分(即s.First + s.Second的逻辑) var expressionBody = Expression.Property(getSumCall, "Body"); MemberInfo member = type.GetMember(propertyName).First(); // 直接用提取后的逻辑作为Sum属性的赋值来源 return Expression.Bind(member, expressionBody); }
最终生成的表达式等价于x => new Target { Sum = x.First + x.Second },完全兼容EF的SQL解析逻辑。
方案2:编译执行表达式(仅适用于内存集合)
如果是对内存中的集合进行操作(比如已调用ToList()之后),可以编译并执行GetSum返回的表达式来获取int值:
private static MemberBinding _getMethodAssigned( string propertyName, MethodInfo methodInfo, ParameterExpression parameter, Type type ) { // 调用GetSum得到Expression<Func<int>>的表达式 var getSumCall = Expression.Call(null, methodInfo, parameter); // 构建编译表达式并执行的逻辑:expression.Compile().Invoke() var compileMethod = typeof(Expression).GetMethod("Compile", Type.EmptyTypes); var compileCall = Expression.Call(getSumCall, compileMethod); var invokeMethod = typeof(Delegate).GetMethod("Invoke"); var invokeCall = Expression.Call(compileCall, invokeMethod); MemberInfo member = type.GetMember(propertyName).First(); return Expression.Bind(member, invokeCall); }
注意:此方案不能用于EF数据库查询,因为EF无法将Compile()和Invoke()解析为SQL语句。
内容的提问来源于stack exchange,提问作者Matthias Burger
相关产品推荐
相关产品推荐

