score:2

Accepted answer

It's failing because SelectMany<TSource, TResult> method expects

Expression<Func<TSource, IEnumerable<TResult>>>

while you are passing

Expression<Func<TSource, ICollection<TResult>>>

These are not the same and the later is not convertible to the former simply because Expression<TDelegate> is a class, and classes are invariant.

Taking your code, the expected lambda result type is like this:

var par = Expression.Parameter(origType, "x");
var propExpr = Expression.Property(par, property);
var firstGenType = reflectedType.GetGenericArguments()[0];
var resultType = typeof(IEnumerable<>).MakeGenericType(firstGenType);

Now you can either use Expression.Convert to change (cast) the property type:

var lambda = Expression.Lambda(Expression.Convert(propExpr, resultType), par);

or (my preferred) use another Expression.Lambda method overload with explicit delegate type (obtained via Expression.GetFuncType):

var lambda = Expression.Lambda(Expression.GetFuncType(par.Type, resultType), propExpr, par);

Either of these will solve your original issue.

Now before you get the next exception, the following line:

var genericToListMethod = enumerableToListMethod.MakeGenericMethod(new[] { actualType });

is also incorrect (because when you pass "Work.Locations", the actualType will be ICollection<Location>, not Location which ToList expects), so it has to be changed to:

var genericToListMethod = enumerableToListMethod.MakeGenericMethod(new[] { actual.ElementType });

In general you could remove actualType variable and always use IQueryable.ElementType for that purpose.

Finally as a bonus, there is no need to find manually the generic method definitions. Expression.Call has a special overload which allows you to easily "call" static generic (and not only) methods by name. For instance, the SelectMany "call" would be like this:

selectExpression = Expression.Call(
    typeof(Queryable), nameof(Queryable.SelectMany), new [] { origType, firstGenType },
    queryable.Expression, lambda);

and calling Select is similar.

Also there is no need to create additional lambda expression, compile and dynamically invoke it in order to get the resulting IQueryable. The same can be achieved by using IQueryProvider.CreateQuery method:

//var result = Expression.Lambda(selectExpression).Compile().DynamicInvoke() as IQueryable;
var result = queryable.Provider.CreateQuery(selectExpression);

score:0

You use your method with a type of ICollection<T>, but your expression takes a IEnumerable<T> as input. And SelectMany() takes a IQueryable<T> as input. Both IQueryable<T> and ICollection<T> are derived from IEnumerable<T>, but if you need a IQueryable<T> you cant give a ICollection<T>.

This would be the same as the following example:

class MyIEnumerable
{ }
class MyICollection : MyIEnumerable
{ }
class MyIQueryable : MyIEnumerable
{ }
private void MethodWithMyIQueryable(MyIQueryable someObj)
{ }

private void DoSth()
{
    //valid
    MethodWithMyIQueryable(new MyIQueryable());
    //invalid
    MethodWithMyIQueryable(new MyICollection());
}

They share the same inheritance from object, but still have no linear inheritance to each other.

Try casting/converting your ICollection<T> to IEnumerable<T> and then give that as parameter.


Related Articles