My code is a slight revision from the sample here:
I am writing an extension method that allows for performing unionon
any property in the source/destination list and has the following signature
public static IEnumerable<TSource> UnionOn<TSource, TProperty>(
this IEnumerable<TSource> first,
Expression<Func<TSource, TProperty>> expression,
IEnumerable<TSource> second)
{
var finalList = new List<TSource>();
finalList.AddRange(first);
var queryableData = finalList.AsQueryable();
foreach (var item in second.ToList())
{
var propertyValue = expression.Compile().Invoke(item);
// x=>x.ExtendedPropertyId == 'guid_value'
var sourceObjectParam = Expression.Parameter(typeof(TSource), "x");
var propertyName = ((MemberExpression)expression.Body).Member.Name;
var left = Expression.Property(sourceObjectParam, propertyName);
var right = Expression.Constant(propertyValue);
var predicateBody = Expression.Equal(left, right);
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Enumerable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<TSource, Boolean>>(predicateBody, new ParameterExpression[] { sourceObjectParam }));
// **** this line causes runtime error *****
IQueryable<TSource> resultsQuery = queryableData.Provider.CreateQuery<TSource>(whereCallExpression);
if (resultsQuery.ToList().Any())
finalList.Add(item);
}
return finalList;
}
The exception reads on generating the resultsQuery
method:
System.ArgumentException: 'Argument expression is not valid
However, when I look at the debugview of the generated expression in whereCallExpression it looks fine to me:
.Call System.Linq.Enumerable.Where(
.Constant<System.Linq.EnumerableQuery`1[A]>(System.Collections.Generic.List`1[A]),
.Lambda #Lambda1<System.Func`2[A,System.Boolean]>)
.Lambda #Lambda1<System.Func`2[A,System.Boolean]>(A $x) {
$x.ExtendedPropertyId == .Constant<System.Guid>(fadd6b4e-8d97-404c-bcf1-
c5ebd02230a6)
}
Any help will be much appreciated.
There are many inefficiencies in this code. And no real benefit of using expressions when working with IEnumerable
s - Func<..>
parameters similar to standard LINQ Enumerable
methods would be enough.
But to answer your concrete question. The exception is because you are calling CreateQuery
with expression returning IEnumerable<TSource>
(due to Enumerable.Where
method call) while it expects (requires) IQueryable
(or IQueryable<TSource>
) type expression.
The fix is simple - change typeof(Enumerable)
to typeof(Queryable)
in your Expression.Call
(the other parameters are unchanged) and the problem will be gone.
Also note that when you have compile time type TSource
and IQueryable<TSource>
and Expression<Func<TSource, bool>>
variables, there is no need to compose Where
call and CreateQuery
- you could simple use Queryable.Where
extension method directly, e.g. given
var predicate = Expression.Lambda<Func<TSource, bool>>(predicateBody, sourceObjectParam);
the
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
predicate);
IQueryable<TSource> resultsQuery =
queryableData.Provider.CreateQuery<TSource>(whereCallExpression);
can be replaced with
IQueryable<TSource> resultsQuery = queryableData.Where(predicate);
thus avoiding errors like this.