Dynamically cast IEnumerable to IQueryable or dynamically call AsQueryable with LINQ Expressions
我正在动态地创建LINQ查询,到目前为止我做得还不错。
但我被困在了我想不到的地方。在构建该查询的某个时刻,我需要访问实体的enityCollection。像这样:
1
| Expression collection = Expression .Property(entity, typeof(EntityType ).GetProperty("CollectionOfRelatedEntities")); |
然后,我将在该集合上调用"Where"Linq方法:
1 2 3 4 5 6
| MethodCallExpression AfterWhere = Expression .Call(
typeof(Queryable ),
"Where",
new Type [] { typeof(RelatedEntity ) },
collection,
Expression .Lambda<Func <RelatedEntity, bool>>(predicate, new ParameterExpression [] { paramOfRelatedEntity })); |
通常情况下,这是可行的。在这种情况下,不会这样做,因为集合是IEnumerable的,我需要它是iQuery,以便"在哪里"工作。
我试过这个:
1
| Expression .Convert(collection, typeof(IQueryable <RelatedEntity >); |
但它表示无法强制转换,因为EntityCollection不实现iQuery。
我静态地使用AsQueryable来实现我在这里所需要的,所以我尝试动态地模拟:
1
| Expression .Call(collection, typeof(EntityCollection <RelatedEntity >).GetMethod("AsQueryable")); |
但我得到空引用异常。我无法通过反射来达到它。这个asqueryable方法是扩展方法,它是静态的,在queryable类中定义的,所以我尝试了:
1
| Expression .Call(collection, typeof(Queryable ).GetMethod("AsQueryable", BindingFlags .Static)); |
结果相同:"值不能为空"。
我在这里达到了我的极限,而且我刚刚摆脱了想法。
所以,我要问你:
如何将IEnumerable动态转换为iqueryable?
试着这样得到方法:
1 2 3 4 5 6
| var method = typeof(Queryable ).GetMethod(
"AsQueryable",
BindingFlags .Static | BindingFlags .Public,
null,
new [] { typeof(IEnumerable <RelatedEntity >)},
null); |
然后,您应该能够像这样构造对该方法的调用:
1
| Expression.Call(method, collection); |
您的代码的问题是绑定标志很难使用。如果指定了任何绑定标志(如bindingFlags.static),则还必须显式地指定是要bindingFlags.public还是bindingFlags.nonpublic。
第二个问题是有两个可查询的方法——通用方法和非通用方法。提供类型参数数组可以解决这种歧义。
- 试过了。运气不好。我还是得不到。我尝试使用新类型[0]作为第四个参数,我只是按照您编写的方式进行了尝试,但仍然得到了空值。我还检查了msdn,这是asqueryable的签名:public static iqueryable asqueryable(这个ienumerable源);
- 抱歉,我的错误,忘记了IEnumerable<>。但是现在,当我尝试使用如下方法时:expression.call(collection,typeof(queryable).getmethod("asqueryable",bindingflags.static bindingflags.public,null,new[]typeof(ienumerable),null))编译器说:"静态方法需要空实例,非静态方法需要非空实例。"扩展方法是对实例方法调用的,对吗?那么,我该如何克服这个障碍呢?
- 你一路都是对的。我花了一段时间才明白你写的是什么,但现在我明白了。谢谢!
- 很高兴你得到它!对不起,如果我的回答不够清楚。
"And normally that would work. In this case it won't because collection is IEnumerable and I need it to be IQueryable in order"Where" to work."
不,你没有。对于可枚举的,使用Enumerable.Where而不是Queryable.Where。
1 2 3 4 5 6 7 8
| var query =
from customer in Context .Customers
where customer .Id == YourCustomerId // 1
select new
{
Customer = customer,
OrderCount = customer .Orders.Where(order => order .IsOpen).Count() // 2
}; |
第一个"where"决定为Queryable.Where,而第二个"where"决定为Enumerable.Where。这不是问题,也不是效率低下,因为整个表达式是子查询的一部分,所以它仍将被发送到查询提供程序并(f.e.)转换为SQL。
- 你是绝对正确的。我把where调用的类型从queryable改为enumerable,并对每个后续的linq调用都做了同样的操作,它的工作方式就像桃子一样。我还设法让它在传递到何处之前调用asqueryable。
好吧,我想我明白了:
首先,通过反射得到方法,如igor所说:
1
| MethodInfo mi = typeof(Queryable ).GetMethod("AsQueryable", BindingFlags .Static | BindingFlags .Public, null, new [] { typeof(IEnumerable <RelatedEntity >) }, null); |
然后,我使用了不同版本的表达式。调用以克服静态/实例不匹配:
1
| Expression buff = Expression .Call(mi, new[] { collection }); |
最后将其转换为类型化的asqueryable:
1
| Expression final = Expression .Convert(buff, typeof(IQueryable <RelatedEntity >)); |