Reputation: 985
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?
Upvotes: 9
Views: 8401
Reputation: 985
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>));
Upvotes: 0
Reputation:
"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 (f.e.) translated to SQL.
Upvotes: 2
Reputation: 7392
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.
Upvotes: 6