Reputation: 53
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
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)));
}
Queryable.Where<T>
queryParamLeft
passed in. queryParamRet is not neededUpvotes: 2