使用Expression.Call调用SelectMany - 错误的参数

时间:2018-06-05 08:14:21

标签: c# linq expression-trees

我想通过字符串来处理关系。

我有一个人,一个工作和位置连接人N:1工作和工作1:N位置(每个人可以有1个工作,一个工作可以有很多地方)。

我方法的输入:

  1. 人员名单(后来EFCore中的人员可用)
  2. 字符串" Work.Locations"从人到工作
  3. 所以我必须使用表达式调用:  1.在人员名单上列表。选择(x => x.Work)  2.在该结果上有一个list.SelectMany(x => x.Locations)

    当我在SelectMany方法(在TODO上)进行Expression.Call时出现错误

            var selectMethod = typeof(Queryable).GetMethods().Single(a => a.Name == "SelectMany" && 
                a.GetGenericArguments().Length == 2 &&
                a.MakeGenericMethod(typeof(object), typeof(object)).GetParameters()[1].ParameterType == 
                typeof(Expression<Func<object, IEnumerable<object>>>));
    
            var par = Expression.Parameter(origType, "x");
            var propExpr = Expression.Property(par, property);
            var lambda = Expression.Lambda(propExpr, par);
    
            var firstGenType = reflectedType.GetGenericArguments()[0];
    
            //TODO: why do I get an exception here?
            selectExpression = Expression.Call(null,
                selectMethod.MakeGenericMethod(new Type[] {origType, firstGenType}),
                new Expression[] { queryable.Expression, lambda});
    

    我得到了这个例外:

      

    System.ArgumentException:&#39;类型的表达式   &#39; {System.Func {1}} 1 [GenericResourceLoading.Data.Location]]&#39;   不能用于类型的参数   &#39; {System.Linq.Expressions.Expression {1}} 2 [GenericResourceLoading.Data.Work,System.Collections.Generic.IEnumerable 2[GenericResourceLoading.Data.Work,System.Collections.Generic.ICollection 1 [GenericResourceLoading.Data.Location]   的SelectMany [工作,位置](System.Linq.IQueryable 1[System.Func 1 [System.Func 1[GenericResourceLoading.Data.Location]]]' of method 'System.Linq.IQueryable 1 [GenericResourceLoading.Data.Location]]])&#39;&#39;

    我的完整代码如下:

    1[GenericResourceLoading.Data.Work],
      System.Linq.Expressions.Expression

2 个答案:

答案 0 :(得分:2)

失败,因为SelectMany<TSource, TResult>方法需要

Expression<Func<TSource, IEnumerable<TResult>>>

当你传球时

Expression<Func<TSource, ICollection<TResult>>>

这些不一样,后者不能转换为前者只是因为Expression<TDelegate>,而且类是不变的。

获取代码,预期的lambda结果类型如下:

var par = Expression.Parameter(origType, "x");
var propExpr = Expression.Property(par, property);
var firstGenType = reflectedType.GetGenericArguments()[0];
var resultType = typeof(IEnumerable<>).MakeGenericType(firstGenType);

现在您可以使用Expression.Convert更改(强制转换)属性类型:

var lambda = Expression.Lambda(Expression.Convert(propExpr, resultType), par);

或(我的首选)使用另一个Expression.Lambda方法重载与显式委托类型(通过Expression.GetFuncType获得):

var lambda = Expression.Lambda(Expression.GetFuncType(par.Type, resultType), propExpr, par);

其中任何一个都可以解决您的原始问题。

现在,在您收到下一个异常之前,请执行以下行:

var genericToListMethod = enumerableToListMethod.MakeGenericMethod(new[] { actualType });

也不正确(因为当您传递“Work.Locations”时,actualType将是ICollection<Location>,而不是Location所期望的ToList,所以它必须改为:

var genericToListMethod = enumerableToListMethod.MakeGenericMethod(new[] { actual.ElementType });

一般情况下,您可以移除actualType变量,并始终使用IQueryable.ElementType来实现此目的。

最后作为奖励,无需手动查找通用方法定义。 Expression.Call有一个特殊的重载,允许您通过名称轻松“调用”静态通用(而不仅仅是)方法。例如,SelectMany“呼叫”将是这样的:

selectExpression = Expression.Call(
    typeof(Queryable), nameof(Queryable.SelectMany), new [] { origType, firstGenType },
    queryable.Expression, lambda);

并且调用Select类似。

此外,无需创建其他lambda表达式,编译并动态调用它以获得结果IQueryable。使用IQueryProvider.CreateQuery方法可以实现同样的目的:

//var result = Expression.Lambda(selectExpression).Compile().DynamicInvoke() as IQueryable;
var result = queryable.Provider.CreateQuery(selectExpression);

答案 1 :(得分:0)

您使用类型为IEnumerable<T>的方法,但您的表达式需要SelectMany()作为输入。 IQueryable<T>需要IQueryable<T>作为输入。 ICollection<T>IEnumerable<T>都来自IQueryable<T>,但如果您需要ICollection<T>,则无法提供class MyIEnumerable { } class MyICollection : MyIEnumerable { } class MyIQueryable : MyIEnumerable { } private void MethodWithMyIQueryable(MyIQueryable someObj) { } private void DoSth() { //valid MethodWithMyIQueryable(new MyIQueryable()); //invalid MethodWithMyIQueryable(new MyICollection()); }

这与以下示例相同:

ICollection<T>

它们与object共享相同的继承,但仍然没有相互的线性继承。

尝试将IEnumerable<T>转换/转换为int main ( ) { int i = 3; //i==3 if (!i) i++; // !3 = False Never Executed i++; // i==4 if (i==3) i+=2; // i!=3, Never Executed i+=2; // i==6 printf("%d", i); return 0; } ,然后将其作为参数。