c# - 使用 Expression.Call 调用 SelectMany - 参数错误
问题描述
我想通过字符串来处理关系。
我有一个人、一个工作和一个位置,它们是连接的人 N:1 工作和工作 1:N 位置(每个人可以有 1 个工作,一个工作可以有多个位置)。
我的方法的输入:
- 人员列表(后来 EFCore 中的人员 IQueryable)
- 字符串“Work.Locations”从人到他们的工作
所以我必须用表达式调用: 1. 在人员列表上列出一个 list.Select(x => x.Work) 2. 在该结果上列出一个 list.SelectMany(x => x.Locations)
在 SelectMany 方法上进行 Expression.Call 时出现错误(在 TODO 处)
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:'System.Func
2[GenericResourceLoading.Data.Work,System.Collections.Generic.ICollection
1 [GenericResourceLoading.Data.Location]] 类型的表达式不能用于'System.Linq.Expressions.Expression1[System.Func
2 [GenericResourceLoading.Data.Work,System.Collections.Generic类型的参数.IEnumerable1[GenericResourceLoading.Data.Location]]]' of method 'System.Linq.IQueryable
1[GenericResourceLoading.Data.Location] SelectMany[Work,Location](System.Linq.IQueryable1[GenericResourceLoading.Data.Work], System.Linq.Expressions.Expression
1[System.Func2[GenericResourceLoading.Data.Work,System.Collections.Generic.IEnumerable
1[GenericResourceLoading.Data.Location]]])''
我的完整代码如下所示:
public void LoadGeneric(IQueryable<Person> queryable, string relations)
{
var splitted = relations.Split('.');
var actualType = typeof(Person);
IQueryable actual = queryable;
foreach (var property in splitted)
{
actual = LoadSingleRelation(actual, ref actualType, property);
}
MethodInfo enumerableToListMethod = typeof(Enumerable).GetMethod("ToList", BindingFlags.Public | BindingFlags.Static);
var genericToListMethod = enumerableToListMethod.MakeGenericMethod(new[] { actualType });
var results = genericToListMethod.Invoke(null, new object[] { actual });
}
private IQueryable LoadSingleRelation(IQueryable queryable, ref Type actualType, string property)
{
var origType = actualType;
var prop = actualType.GetProperty(property, BindingFlags.Instance | BindingFlags.Public);
var reflectedType = prop.PropertyType;
actualType = reflectedType;
var isGenericCollection = reflectedType.IsGenericType && reflectedType.GetGenericTypeDefinition() == typeof(ICollection<>);
MethodCallExpression selectExpression;
if (isGenericCollection)
{
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});
}
else
{
var selectMethod = typeof(Queryable).GetMethods().Single(a => a.Name == "Select" &&
a.MakeGenericMethod(typeof(object), typeof(object)).GetParameters()[1].ParameterType ==
typeof(Expression<Func<object, object>>));
var par = Expression.Parameter(origType, "x");
var propExpr = Expression.Property(par, property);
var lambda = Expression.Lambda(propExpr, par);
selectExpression = Expression.Call(null,
selectMethod.MakeGenericMethod(new Type[] {origType, reflectedType}),
new Expression[] {queryable.Expression, lambda});
}
var result = Expression.Lambda(selectExpression).Compile().DynamicInvoke() as IQueryable;
return result;
}
解决方案
它失败了,因为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);
推荐阅读
- node.js - Mongodb查询不到指定时间
- c - 为什么我可以在 C 中定义一个变量两次?
- elasticsearch - 为什么ES中不推荐search_after中使用的_id
- system-calls - RISC-V (RV32I) - SCALL/ECALL、SBREAK/EBREAK、FENCE、FENCE.I
- google-sheets - 有没有更好的方法来嵌套 500 个 if 语句(Google 表格)?
- firebase - 为什么我的 StreamBuilder 只能在热重载后工作?
- ssl - 将 SSL 从 Winlogbeat 配置到 Elasticsearch
- javascript - 使用 XMLHttpRequest 在 MySQL 中显示数据库中的数据
- javascript - 持续时间为负的 PerformanceObserver 标记 api
- r - R在整个时间段内生成高于/低于平均值的虚拟变量?