Reputation: 10959
Preface
I counted probably 20 questions pertaining to this particular error, but I did not find that any of them apply. I am doing something different in that I am creating my expressions programatically rather than by literal lambda syntax. I suspect that this may be unearthing other complications.
The code below uses some of my helpers as well as my own UnitOfWork class. For brevity, I will not drag all of that here, but will provide any additional code upon request.
The question
I am trying to perform IQueryable
operations on a SelectMany
. It works when I write out the expressions literally. However, because I need to be able to build all of this dynamically, I don't have the option of literal expressions.
See code below for comments.
Unable to cast the type 'IQueryable`1[[SystemAssociateModel]]' to type 'IEnumerable`1[[SystemAssociateModel]]'.
LINQ to Entities only supports casting EDM primitive or enumeration types.
☺
public static void SelectManyTest()
{
int id = 14690;
var p = Expression.Parameter(typeof(SystemEntitlement));
var projector = ExpressionHelpers.GetLambda<SystemAssociate, SystemAssociateModel>(x => new SystemAssociateModel
{
role = x.Role.Name,
id = x.Associate.Id,
order = x.Order
});
var memberPath = ExpressionHelpers.GetLambda<SystemEntitlement, ICollection<SystemAssociate>>(
x => x.System.Associates).Body.AsMemberExpression().ReplaceInstance(p);
//These two calls equate to this: x.System.Associates.AsQueryable().Select(projector)
var call_asQueryable = Expression.Call(ExpressionHelpers.GetMethodInfo(() => Queryable.AsQueryable<SystemAssociate>(null)), memberPath);
Expression call_select = Expression.Call(ExpressionHelpers.GetMethodInfo(
() => Queryable.Select(default(IQueryable<SystemAssociate>), default(Expression<Func<SystemAssociate, SystemAssociateModel>>))),
call_asQueryable, projector);
//I use this in an attempt to cast my `Select` into `IEnumerable` for `SelectMany`. This
//is most likely where the issue is occurring. It makes sense that only specific types of
//casts would be supported within an expression.
//call_select = Expression.Convert(call_select, typeof(IEnumerable<SystemAssociateModel>));
//I have to use the uncommented line since that's the only one suitable for `.SelectMany`.
//This is the reason for the cast above, to convert `Select`'s `IQueryable` into an
//`IEnumerable` for `SelectMany`. If I don't use the explicit cast, it will attempt an
//implicit cast and still fail.
//var selector = (Expression<Func<SystemEntitlement, IQueryable<SystemAssociateModel>>>)Expression.Lambda(call_select, masterp);
var selector = (Expression<Func<SystemEntitlement, IEnumerable<SystemAssociateModel>>>)Expression.Lambda(call_select, p);
//This works so long as I write the `.SelectMany` expression literally. I seems that the compiler is somehow
//able to handle the implicit cast from `ICollection` to `IEnumerable`.
var associates1 = UnitOfWork.UseWith(work => work.GetRepo<SystemEntitlement>().AsQueryable()
.Where(x => x.Id == id)
.SelectMany(x => x.System.Associates.AsQueryable().Select(projector))
.Where(x => x.order == 1)
.ToArray());
//This throws the error in question.
var associates2 = UnitOfWork.UseWith(work => work.GetRepo<SystemEntitlement>().AsQueryable()
.Where(pred)
.SelectMany(selector)
.Where(x => x.order == 1)
.ToArray());
}
Upvotes: 1
Views: 841
Reputation: 205769
EF does not support such casts, so no Expression.Convert
should be used.
The actual problem is that Expression.Lambda(call_select, p)
returns Expression<Func<T, IQueryable<R>>>
which you are trying to cast to Expression<Func<T, IEnumerable<R>>>
. But Expression
as any C# class does not support variance, hence the cast expectedly fails.
The solution is to use Expression.Lambda
method overloads which allow you to specify the desired result type. In your sample, it could be like this:
var selector = Expression.Lambda<Func<SystemEntitlement, IEnumerable<SystemAssociateModel>>>(
call_select, p);
Once you do that, the problem will be solved.
As a side note - when you build expressions dynamically, there is no need of applying AsQueryable
on collection navigation properties - it's used to trick the C# compiler to allow using Expression<Func<...>>
variable inside what you call literal expressions in methods that expect Func<...>
. When you emit Expression.Call
by hand, you could simply call the corresponding Enumerable
method:
var call_select = Expression.Call(ExpressionHelpers.GetMethodInfo(
() => Enumerable.Select(default(IEnumerable<SystemAssociate>), default(Func<SystemAssociate, SystemAssociateModel>))),
memberPath, projector);
Here I'm guessing the expected arguments of your ExpressionHelpers.GetMethodInfo
. I personally "call" generic methods like this:
var call_select = Expression.Call(
typeof(Enumerable), "Select", new[] { typeof(SystemAssociate), typeof(SystemAssociateModel) },
memberPath, projector);
Of course AsQueryable
doesn't hurt in EF6, but is redundant and is not supported in EF Core.
Upvotes: 2