将IEnumerable动态强制转换为IQueryable,或使用LINQ表达式动态调用AsQueryable

本文关键字:动态 LINQ 调用 AsQueryable 表达式 IQueryable IEnumerable 转换 | 更新日期: 2023-09-27 18:25:04

我正在动态创建LINQ查询,到目前为止我做得还可以。

但我被困在了我认为不会出现的地方。在构建查询的某个时刻,我需要访问实体的EnityCollection。类似这样的东西:

Expression collection = Expression.Property(entity, typeof(EntityType).GetProperty("CollectionOfRelatedEntities"));

然后,我会对该集合调用"Where"LINQ方法:

MethodCallExpression AfterWhere = Expression.Call(
                        typeof(Queryable),
                        "Where",
                        new Type[] { typeof(RelatedEntity) },
                        collection,
                        Expression.Lambda<Func<RelatedEntity, bool>>(predicate, new ParameterExpression[] { paramOfRelatedEntity }));

通常情况下,这是可行的。在这种情况下,它不会,因为集合是IEnumerable的,我需要它是IQueryable的,以便"在哪里"工作。

我试过这个:

Expression.Convert(collection, typeof(IQueryable<RelatedEntity>);

但它表示无法强制转换,因为EntityCollection没有实现IQueryable。

我静态地使用AsQueryable来实现我在这里需要的东西,所以我尝试动态地模仿它:

Expression.Call(collection, typeof(EntityCollection<RelatedEntity>).GetMethod("AsQueryable"));

但我得到了null引用异常。我无法通过反射来达到它。这个AsQueryable方法是扩展方法,它是静态的,在Queryable类中定义,所以我尝试了:

Expression.Call(collection, typeof(Queryable).GetMethod("AsQueryable", BindingFlags.Static)); 

相同的结果:"值不能为null"。

我在这里已经达到了极限,而且我还没有什么想法。

所以,我问你:

如何将IEnumerable动态转换为IQueryable?

将IEnumerable动态强制转换为IQueryable,或使用LINQ表达式动态调用AsQueryable

尝试以这种方式获取方法:

var method = typeof(Queryable).GetMethod(
    "AsQueryable",
    BindingFlags.Static | BindingFlags.Public, 
    null, 
    new [] { typeof(IEnumerable<RelatedEntity>)}, 
    null);

然后,您应该能够构造对该方法的调用,如下所示:

Expression.Call(method, collection);

代码的问题在于BindingFlags的使用很难。如果指定任何BindingFlags(如BindingFlags.Static),则必须明确表示是要BindingFlags.Public还是BindingFlags.NonPublic.

第二个问题是有两个AsQueryable方法——一个通用方法和一个非通用方法。提供类型参数数组可以解决这种歧义。

"通常情况下,这会起作用。在这种情况下,它不会起作用,因为集合是IEnumerable的,我需要它是IQueryable的,才能在哪里工作。"

不,你没有。对于可枚举项,请使用Enumerable.Where而不是Queryable.Where

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,但第二个不解析,即Enumerable.Where。这不是问题或效率低下,因为整个表达式都是子查询的一部分,所以它仍将发送到查询提供程序并(例如)转换为SQL。

好吧,我想我明白了:

首先,通过反射获得方法,正如Igor所说:

MethodInfo mi = typeof(Queryable).GetMethod("AsQueryable", BindingFlags.Static | BindingFlags.Public, null, new [] { typeof(IEnumerable<RelatedEntity>) }, null);

然后,我使用了不同版本的Expression.Call来克服静态/实例不匹配:

Expression buff = Expression.Call(mi, new[] { collection });

并最终将其转换为类型化的AsQueryable:

Expression final = Expression.Convert(buff, typeof(IQueryable<RelatedEntity>));