Home > Software engineering >  ArgumentException when trying to translate efcore expression to expression trees
ArgumentException when trying to translate efcore expression to expression trees

Time:08-27

I am trying to build following query

_dbContext.Models.Where(m => m.Children.Any(c => c.Name.Contains("Foobar")))

using expression trees. I am not very familar with expression trees yet, quite frankly I find them challenging. What I've tried:

var toCompare = "Foobar";
var param = Expression.Parameter(typeof(T));

// Properties

var propertyLeft = Expression.Property(param, "Children");
// Trying to retrieve Name property of the Child type
var propertyRight = Expression.Property(Expression.Parameter(propertyLeft.Type.GetGenericArguments().Single()), "Name");

// Methods

var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;

var any = typeof(Queryable).GetMethods()
    .Where(m => m.Name == "Any")
    .First(m => m.GetParameters().Length == 2)
    .MakeGenericMethod(typeof(string));

// Build c => c.Name.Contains(toCompare)

var expContains = Expression.Call(propertyRight, contains, Expression.Constant(toCompare, propertyRight.Type));
var expContainsLambda = Expression.Lambda<Func<T, bool>>(expContains, parameterExpression);

// Build m => m.Children.Any(...)

var expAny = Expression.Call(any, Expression.Constant(toCompare), expContainsLambda);
var expAnyLambda = Expression.Lambda<Func<Expression<Func<T, string>>, bool>>(expAny, parameterExpression);

// Build Models.Where(m => ...)

var expWhere = Expression.Lambda<Func<T, bool>>(expAnyLambda, param);

I am getting following error at expAny

System.ArgumentException: Expression of type 'System.String' cannot be used for parameter of type 'System.Linq.IQueryable`1[System.String]' of method 'Boolean Any[String](System.Linq.IQueryable`1[System.String], System.Linq.Expressions.Expression`1[System.Func`2[System.String,System.Boolean]])' (Parameter 'arg0')

Any ideas why? What changes are necessary to make it work?

CodePudding user response:

Try the following:

var toCompare = "Foobar";

var param = Expression.Parameter(typeof(T), "m");

// m.Children
var propertyLeft = Expression.Property(param, "Children");

var childType = propertyLeft.Type.GetGenericArguments()[0];
var childParam = Expression.Parameter(childType, "c");

// c.Name
var propertyRight = Expression.Property(childParam, "Name");

// c => c.Name.Contains("Foobar")
var anyPredicate = 
    Expression.Lambda(
        Expression.Call(propertyRight, nameof(string.Contains), Type.EmptyTypes, Expression.Constant(toCompare)),
        childParam
    )

// m.Children.Any(c => c.Name.Contains("Foobar"))
var anyCall = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new [] { childType }, propertyLeft, anyPredicate);

// m => m.Children.Any(c => c.Name.Contains("Foobar"))
var expWhere = Expression.Lambda<Func<T, bool>>(anyCall, param);
  • Related