我想通过字符串来处理关系。
我有一个人,一个工作和位置连接人N:1工作和工作1:N位置(每个人可以有1个工作,一个工作可以有很多地方)。
我方法的输入:
所以我必须使用表达式调用: 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.IQueryable1[System.Func
1 [System.Func1[GenericResourceLoading.Data.Location]]]' of method 'System.Linq.IQueryable
1 [GenericResourceLoading.Data.Location]]])&#39;&#39;
我的完整代码如下:
1[GenericResourceLoading.Data.Work],
System.Linq.Expressions.Expression
答案 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;
}
,然后将其作为参数。