匿名对象动态Where条件报错:表达式类型不匹配如何解决?
问题
我想要实现一个接收Expression<Func<Class1, Class2, Class3, bool>>类型参数的Get方法,代码如下:
public async Task<IList<T>> Get(Expression<Func<Class1, Class2, Class3, bool>> predicate) { var query = from c1 in db.Class1 from c2 in db.Class2 from c3 in db.Class3 select new { c1, c2, c3 }; return query.Where(predicate).ToListAsync(); }
调用时希望传入Get((x, y, z) => x.Prop1 == 1 && y.Prop5 == 4)这样的表达式,但在Where(predicate)行出现错误:
Error CS1503 Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<Class1, Class2, Class3, bool>>' to 'System.Linq.Expressions.Expression<System.Func<<anonymous type: Class1 pcp, Class2 pc, Class3 bu>, bool>>'
请问该如何解决这个问题?
解决方法
错误根源在于查询返回的是包含c1、c2、c3的匿名类型,但Where方法需要接收该匿名类型的表达式,而你传入的是接收三个独立实体参数的表达式,两者类型不匹配。以下是几种可行的解决方式:
方式一:用值元组替代匿名类型
把查询的select部分改为返回值元组,再将原表达式转换为适配元组的形式:
// 建议调整返回类型为明确的元组集合,避免泛型T的歧义 public async Task<IList<(Class1, Class2, Class3)>> Get(Expression<Func<Class1, Class2, Class3, bool>> predicate) { var query = from c1 in db.Class1 from c2 in db.Class2 from c3 in db.Class3 select (c1, c2, c3); // 返回值元组 // 将接收三个参数的表达式转换为接收元组的表达式 var tupleParam = Expression.Parameter(typeof((Class1, Class2, Class3)), "tuple"); var newBody = Expression.Invoke( predicate, Expression.Property(tupleParam, nameof(ValueTuple<Class1, Class2, Class3>.Item1)), Expression.Property(tupleParam, nameof(ValueTuple<Class1, Class2, Class3>.Item2)), Expression.Property(tupleParam, nameof(ValueTuple<Class1, Class2, Class3>.Item3)) ); var tuplePredicate = Expression.Lambda<Func<(Class1, Class2, Class3), bool>>(newBody, tupleParam); return await query.Where(tuplePredicate).ToListAsync(); }
调用方式保持不变:await Get((x, y, z) => x.Prop1 == 1 && y.Prop5 == 4);
方式二:直接适配匿名类型的表达式
修改方法参数为针对匿名类型的表达式,调用时通过类型推断传递:
public async Task<IList<dynamic>> Get(Expression<Func<dynamic, bool>> predicate) { var query = from c1 in db.Class1 from c2 in db.Class2 from c3 in db.Class3 select new { c1, c2, c3 }; return await query.Where(predicate).ToListAsync(); } // 调用时调整写法: await Get(anon => anon.c1.Prop1 == 1 && anon.c2.Prop5 == 4);
方式三:表达式树替换适配匿名类型
如果必须保留原方法的参数类型,可以通过自定义表达式访问器替换参数节点,适配匿名类型:
public async Task<IList<dynamic>> Get(Expression<Func<Class1, Class2, Class3, bool>> predicate) { var query = from c1 in db.Class1 from c2 in db.Class2 from c3 in db.Class3 select new { c1, c2, c3 }; // 创建匿名类型的参数表达式 var anonParam = Expression.Parameter(query.ElementType, "anon"); // 替换原表达式中的三个参数为匿名类型的属性 var visitor = new ParamReplaceVisitor( (predicate.Parameters[0], Expression.Property(anonParam, "c1")), (predicate.Parameters[1], Expression.Property(anonParam, "c2")), (predicate.Parameters[2], Expression.Property(anonParam, "c3")) ); var newBody = visitor.Visit(predicate.Body); var anonPredicate = Expression.Lambda(newBody, anonParam); return await query.Where((dynamic)anonPredicate).ToListAsync(); } // 自定义表达式参数替换访问器 private class ParamReplaceVisitor : ExpressionVisitor { private readonly Dictionary<ParameterExpression, Expression> _replaceMap; public ParamReplaceVisitor(params (ParameterExpression Old, Expression New)[] replacements) { _replaceMap = replacements.ToDict(r => r.Old, r => r.New); } protected override Expression VisitParameter(ParameterExpression node) { if (_replaceMap.TryGetValue(node, out var replacement)) return replacement; return base.VisitParameter(node); } }
内容的提问来源于stack exchange,提问作者Pavol
相关产品推荐
相关产品推荐

