Search code examples
c#reflectionexpressionsystem.reflection

IQueryable expression type mismatch


I want to create an extension method for a LINQ expression but I'm stuck. What I need is just to create a method which will add a specific Where clause to a Queryable. Something like:

var hierarchy = "a string";
Session.Query<SomeClass>.Where(x => x.Layer.Hierarchy.StartsWith(hierarchy) ||
                                    x.Layer.Hierarchy == hierarchy);

to become:

var hierarchy = "a string";
Session.Query<SomeClass>.LayerHierarchy(x => x.Layer, hierarchy);

And do that Where logic inside. So basicly the extension method LayerHierarchy() is running over the Queryable of T but the subject is of type Layer:

public static IQueryable<T> LayerHierarchy<T>(this IQueryable<T> query,
                                                  Expression<Func<T, Layer>> layer,
                                                  string hierarchy)

{
    var parameterExp = Expression.Parameter(typeof(Layer), "layer");
    var propertyExp = Expression.Property(parameterExp, "Hierarchy");

    // StartWith method
    MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));

    var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
    var startsWith = Expression.Lambda<Func<Layer, bool>>(methodExpStartsWith, parameterExp);

    // Equals method
    MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
    var valueEquals = Expression.Constant(hierarchy, typeof(string));

    var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
    var equals = Expression.Lambda<Func<Layer, bool>>(methodExpEquals, parameterExp);

    return query
                .Where(startsWith)
                .Where(equals);
}

Everything works fine above the return line. It complains that...

Cannot convert from System.Linq.Expressions.Expression<System.Func<Layer, bool>> to System.Linq.Expressions.Expression<System.Func<T, int, bool>>

when trying to pass the expressions to query.Where() method. How can I fix it?


Solution

  • Well, the problem is how you are creating the Lambdas. They should begin from T, not from Layer:

    var startsWith = Expression.Lambda<Func<T, bool>>(methodExpStartsWith, parameterExp);
    var equals = Expression.Lambda<Func<T, bool>>(methodExpEquals, parameterExp);
    

    However, in order for this to work, you are missing one more PropertyExpression.

    Your query now looks like:

    (Layer)x => x.Hierarchy.StartsWith(...)

    When, what you want is this:

    (T)x => x.Layer.Hierarchy.StartsWith(...)

    So, use this instead:

    var parameterExp = Expression.Parameter(typeof(T), "item");
    var layerExp = Expression.Property(parameterExp, "Layer");
    var propertyExp = Expression.Property(layerExp, "Hierarchy");
    

    Your logic should change a little though, since two .Where will generate an AND condition between them, and it seems like you want one of them to be true (StartsWith or Equals), so:

    var parameterExp = Expression.Parameter(typeof(T), "item");
    var layerExp = Expression.Property(parameterExp, "Layer");
    var propertyExp = Expression.Property(layerExp, "Hierarchy");
    
    // StartWith method
    MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
    
    var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
    
    // Equals method
    MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
    var valueEquals = Expression.Constant(hierarchy, typeof(string));
    
    var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
    
    var orElseExp = Expression.OrElse(methodExpStartsWith, methodExpEquals);
    var orElse = Expression.Lambda<Func<T, bool>>(orElseExp, parameterExp);
    
    return query.Where(orElse);