Rune Solberg
Rune Solberg

Reputation: 53

Build LambdaExpression with Func<,> where returnType is IQueryable<T>

I am trying to create a generic way of getting an EntityFramework object based on its own id without passing in a lambda expression as parameter to the method GetById(). For the code below the entity T is of type Message, is known to the class where GetById() is implemented and has a property MessageId along with several other properties. The MessageId name has been hard-coded in the example below as this is still experimental - extracting the id property name from T is quite easy to fix later.

I have been struggling to find a way to construct a simple LambdaExpression which has IQueryable<T> as parameter type and hope that someone would have a clue on how this could be done. The reason why I want IQueryable<T> is because my underlying channel factory provider requires this for more complex queries.

The line with var exp = Expression.Lambda<...> in the code below shows the expression function type definition which I want to end up with, but the line gives the exception:

Expression of type System.Boolean cannot be used for return type IQueryable

That's because the body has the Boolean type while my expression parameter queryParamtRet is of type IQueryable<Message>. Further, if I change the body type to be an IQueryable<Message>, I'm not able to find the property MessageId since the type is no longer type T as Message but type IQueryable<T>.

public T GetById(int id)
{
    var queryParamLeft = Expression
        .Parameter(typeof(System.Data.Entity.DbSet<T>), "o");
    var queryParamRet = Expression
        .Parameter(typeof(IQueryable<T>), "o");
    var entityFrameworkType = Expression
        .Parameter(typeof(T), "o");
    var queryProperty = Expression
        .PropertyOrField(entityFrameworkType, "MessageId");
    var body = Expression
        .Equal(queryProperty, Expression.Constant(id));
    var exp = Expression
        .Lambda<Func<System.Data.Entity.DbSet<T>, IQueryable<T>>>(
            body,
            queryParamRet);

    var returnXml = DoWithChannel(channel
                        => channel.Load(serializer.Serialize(exp)));
}

Upvotes: 2

Views: 486

Answers (1)

Grax32
Grax32

Reputation: 4059

TLDR: Write out code that you want to create an expression for, then deliberately create the expression, starting with any inner expressions before combining them into the outer expression.


If you write your intended code as a function, it would look something like this

public static IQueryable<T> FilterADbSet(DbSet<T> dbSet)
{
    return Queryable.Where<T>(dbSet, o => o.MessageId == 34);
}

It has one input parameter of type DbSet<T>, an output of type IQueryable<T> and it calls Queryable.Where<T> with parameters of the dbSet variable and an expression.

Working from the outside in, you first need to build the expression to pass to the where clause. You have already done that in your code.

Next you need to create a lambda expression for the where clause.

var whereClause = Expression.Equal(queryProperty, Expression.Constant(id));

var whereClauseLambda = Expression.Lambda<Func<T, bool>>(whereClause, entityFrameworkType);

Next, as the comments indicate, you need to use Expression.Call to create a body.

My end result with making your code work is below.

static Expression<Func<IQueryable<T>, IQueryable<T>>> WhereMethodExpression = v => v.Where(z => true);
static MethodInfo WhereMethod = ((MethodCallExpression)WhereMethodExpression.Body).Method;

public T GetById(int id)
{
    var queryParamLeft = Expression
        .Parameter(typeof(System.Data.Entity.DbSet<T>), "dbSet");

    var entityFrameworkType = Expression
        .Parameter(typeof(T), "entity");

    var queryProperty = Expression
        .PropertyOrField(entityFrameworkType, "MessageId");

    var whereClause = Expression
        .Equal(queryProperty, Expression.Constant(id));

    var whereClauseLambda = Expression.Lambda<Func<T, bool>>(whereClause, entityFrameworkType);

    var body = Expression.Call(
        WhereMethod,
        queryParamLeft,
        whereClauseLambda
        );

    var exp = Expression
        .Lambda<Func<System.Data.Entity.DbSet<T>, IQueryable<T>>>(
            body,
            queryParamLeft);

    var returnXml = DoWithChannel(channel
                        => channel.Load(serializer.Serialize(exp)));

}
  1. I used an expression to fetch the MethodInfo object of Queryable.Where<T>
  2. Your body expression needed queryParamLeft passed in. queryParamRet is not needed

Upvotes: 2

Related Questions