简体   繁体   中英

Dynamically cast IEnumerable to IQueryable or dynamically call AsQueryable with LINQ Expressions

I am creating LINQ query dynamically, and I am doing OK so far.

But I am stuck where I thought I wouldn't be. At a certain point in building that query, I need to access EnityCollection of an entity. Something like this:

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

Then, I would call "Where" LINQ method upon that collection:

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

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.

I tried this:

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

but it says unable to cast because EntityCollection doesn't implement IQueryable.

I statically use AsQueryable to achieve what i need here, so i tried mimicking that dynamically:

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

but I get null reference exception. I can't reach it via reflection. This AsQueryable method is extension method, it is static, defined in Queryable class, so i tried:

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

Same result: "Value cannot be null".

I am reaching my limits here, and I am fresh out of ideas.

So, I am asking you:

How can I dynamically cast IEnumerable to IQueryable?

Try to get the method this way:

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

Then, you should be able to construct a call to that method like this:

Expression.Call(method, collection);

The problem with your code was that BindingFlags are tricky to use. If you specify any BindingFlags - like BindingFlags.Static - then you also have to explicitly say whether you want BindingFlags.Public or BindingFlags.NonPublic.

Then the second problem is that there are two AsQueryable methods - a generic one and a non-generic one. Providing the array of type arguments resolves that ambiguity.

"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."

No, you don't. For an enumerable, use Enumerable.Where instead of 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
    };

The first "where" resolves to Queryable.Where , but the second doesn't, that's Enumerable.Where . That isn't a problem or inefficient, because the whole expression is part of a subquery, so it will still be sent to the query provider and (fe) translated to SQL.

Ok, I think I got it:

First, get the method via reflection as Igor said:

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

then, I used different version of Expression.Call to overcome static/instance mismatch:

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

and finally cast it to a typed AsQueryable:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM