Rewrite Lambda expression with System.Linq.Expressions APIs - c#

I am stuck on this for hours. All I want to do is build an Expression tree by rewriting this following expression using Expression class APIs:
var Expression<Func<T, bool>> expr = x => x.SomeProperty == value;
What I got so far are:
{
var param = Expression.Parameter(typeof(T), "x");
var lhs = Expression.Property(param, "SomeProperty");
var rhs = Expression.Constant(value, value.GetType());
return Expression.Call(typeof(object).GetMethod("Equals", BindingFlags.Static | BindingFlags.Public), lhs, rhs);
}
This works fine if T is a primitive type or enumeration. But I got an exception if T is a reference type, a class etc.
Exception Message:
Unable to create a constant value of type 'TypeName'. Only primitive
types or enumeration types are supported in this context.
Thanks in advance.

You don't need to specify the type explicitly, in this case, as long as the value is not null (which I'm assuming it isn't, as you're calling GetType() on it).
This should do it.
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, "SomeProperty");
var compareValue = Expression.Constant(value);
var equals = Expression.Equal(property, compareValue);
return Expression.Lambda<Func<T, bool>>(equals, param);

The expression generated was passed to a Linq Where call. Like this.
Context.Sources.Where(criteria.BuildQuery());
The exception was thrown when the expression is being evaluated/translated.
If I compile the expression and then pass a delegate to the Where call, everything works as expected.
Context.Sources.Where(criteria.BuildQuery().Compile());
I am not sure what difference it makes, if someone knows why please enlighten us.

Related

Linq extending Expressions

I'm trying to write a generic wildcard Search for the ServiceStack.OrmLite.SqlExpressionVisitor that has the following signature:
public static SqlExpressionVisitor<T> WhereWildcardSearch<T> (this SqlExpressionVisitor<T> ev, Expression<Func<T,string>> field, string search)
where ev is the rest of the filter, field is the getter for the field to search by and search is the entered term.
Normally (non-generic) I would write the following:
if(search.StartsWith('*') && search.EndsWith('*'))
ev = ev.Where(x => x.foo.Contains(search.Trim('*')));
and of course also variants for x.foo.StartsWith or EndsWith.
Now I am searching for something like (pseudocode:)
ev = ev.Where(x => field(x).Contains(search.Trim('*')));
Of course I can't compile and call the expression directly, as this should be translated to Sql using Linq2Sql.
This is my code so far:
var getFieldExpression = Expression.Invoke (field, Expression.Parameter (typeof (T), "getFieldParam"));
var searchConstant = Expression.Constant (search.Trim('*'));
var inExp = Expression.Call (getFieldExpression, typeof(String).GetMethod("Contains"), searchConstant);
var param = Expression.Parameter (typeof (T), "object");
var exp = Expression.Lambda<Func<T, bool>> (inExp, param);
ev = ev.Where (exp);
Please don't tell me that I should directly write SQL with $"LIKE %search%" or something - I know that there are other ways, but solving this would help my understanding of Linq and Expressions in general and it bugs me when I can't solve it.
Here is how it can be done (I think it will be clear for you without much additional explanations what you did wrong, but if not - feel free to request a clarification):
// extract property name from passed expression
var propertyName = ((MemberExpression)field.Body).Member.Name;
var param = Expression.Parameter(typeof(T), "object");
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// object.FieldName.Contains(searchConstant)
var inExp = Expression.Call(Expression.PropertyOrField(param, propertyName), contains, searchConstant);
// object => object.FieldName.Contains(searchConstant)
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In response to comment. You have two expression trees: one is being passed to you and another one which you are building (exp). In this simple case they both use the same number of parameters and those parameters are of the same type (T). In this case you can reuse parameter from field expression tree, like this:
// use the same parameter
var param = field.Parameters[0];
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// note field.Body here. Your `field` expression is "parameter => parameter.Something"
// but we need just "parameter.Something" expression here
var inExp = Expression.Call(field.Body, contains, searchConstant);
// pass the same parameter to new tree
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In more complicated cases you might need to use ExpressionVisitor to replace parameters in one expression tree to reference to parameters from another (final) expression tree.

C# Generate Expression<Func<Foo, object>>

I am using a library containing a function which takes an expression parameter, x => x.Name.
Using reflection, I am trying to call this for each property of Foo. This means that I need to create an expression of type Expression<Func<Foo, object>> for each property.
I have been reading about expression trees, but nothing that covers this case.
foreach (var property in typeof(Foo).GetProperties())
{
ParameterExpression pe = Expression.Parameter(typeof(Foo), "x");
MemberExpression me = Expression.Property(pe, property.Name);
Expression<Func<Foo, object>> expression = ... // x => x.Name
Bar(expression, property.Name);
}
Solved:
foreach (var property in typeof(Foo).GetProperties())
{
ParameterExpression pe = Expression.Parameter(typeof(Foo), "x");
MemberExpression me = Expression.Property(pe, property.Name);
var expression = (Expression<Func<Foo, object>>)
Expression.Lambda(Expression.Convert(me, typeof(object)), pe);
Bar(expression, property.Name);
}
The problem is that you apparently do not know the type Foo at compile-time, otherwise you could simply create a lambda expression using the generic Expression.Lambda overload and be fine.
There is a overload Expression.Lambda that will create a LambdaExpression and derives the type parameters from the expression you specified. This means, it will deduct it from the parameter expression and the member expression you use as body.
In both implementations, there is a catch, though. The return type object is a reference type. The properties may however return a value type (such as int), which requires casting. Normally, the compiler does that for you. In this case, you have to do that by yourself.
private IEnumerable<LambdaExpression> CreateExpressions(Type fooType)
{
foreach (var property in fooType.GetProperties())
{
ParameterExpression pe = Expression.Parameter(fooType, "x");
MemberExpression me = Expression.Property(pe, property.Name);
yield return Expression.Lambda(Expression.Convert(me, typeef(object)), pe);
}
}
If you know the type Foo at compile-time, you can simply add the generic parameter Func<Foo, object> to the Lambda method.

Linq Expressions -> Create CallExpression with Equals using enum types

I'm trying to build an CallExpression like:
f.Equals(s);
where, f and t are enum SType.
So,
var equalsMethod = typeof(SType).GetMethod("Equals", new[] { typeof(SType) });
ConstantExpression constantExpression = Expression.Constant(value, typeof(SType));
var newBody = Expression.Call(expr.Body, equalsMethod, constantExpression);
return Expression.Lambda<Func<TEntity, bool>>(newBody, expr.Parameters);
I don't know, but equalsMethod is Boolean Equals(System.Object) instead of Boolean Equals(SType).
So, when I want to build CallExpression .Net tells me, I'm not able to use an expression of type SType for the parameter of type System.Object of the method Boolean Equals(System.Object).
What's wrong?
When you call f.Equals(s) you're really doing:
f.Equals((object)s)
... because Enum and ValueType don't overload Equals. So basically you need a conversion in there - and you can be clearer about the Equals method you're calling, too:
var equalsMethod = typeof(object).GetMethod("Equals", new[] { typeof(object) });
var constant = Expression.Constant(value, typeof(SType));
var boxed = Expression.Convert(constant, typeof(object));
var newBody = Expression.Call(expr.Body, equalsMethod, boxed);
return Expression.Lambda<Func<TEntity, bool>>(newBody, expr.Parameters);
Admittedly you could probably avoid the separate step, just with:
var equalsMethod = typeof(object).GetMethod("Equals", new[] { typeof(object) });
// Let this do the boxing for you...
var constant = Expression.Constant(value, typeof(object));
var newBody = Expression.Call(expr.Body, equalsMethod, constant);
return Expression.Lambda<Func<TEntity, bool>>(newBody, expr.Parameters);
I haven't tried that, but I suspect it will work just fine.
Jon already described what's wrong with your code. However, I'm wondering why you ever bother creating MethodCallExpression to Equals method while there is a specific Expression.Equal method just for that (well, to be precise, for doing equality comparison):
return Expression.Lambda<Func<TEntity, bool>>(
Expression.Equal(expr.Body, Expression.Constant(value)),
expr.Parameters);

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)));
}
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)));
}
I used an expression to fetch the MethodInfo object of Queryable.Where<T>
Your body expression needed queryParamLeft passed in. queryParamRet is not needed

Expression Trees and Nullable Types

I've been playing around with Expression Trees. I have the following simple method that performs a query by dynamically creating an Expression Tree. ItemType is a nullable int in the database, and also in the EF entity class. For some reason though the query throws the error of
Unhandled Exception:
System.InvalidOperationException: The
binary operator Equal is not defined
for the types
'System.Nullable`1[System.Int32]' and
'System.Int32'.
I don't think I'm asking EF to convert anything. I've got my parameter defined as int?, which is what I thought it should be.
Note, I've looked at this
Working with nullable types in Expression Trees
But this guy is trying to pass in his nullable int value typed as object, which EF I guess has problems with. I'm actually declaring this as the right type ab initio.
public void GetResultCollection<T>() {
MyEntities db = new MyEntities();
var result = db.CreateQuery<T>(String.Format("[{0}]", typeof(T).Name + "s"));
int? ItemTypeValue = 1;
var param = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Property(param, "ItemType"),
Expression.Constant(ItemTypeValue)),
param);
var list = result.Where(lambda).ToList();
}
EDIT
I've also tried ItemTypeValue.Value - same error
I think you need to convert it
var right = Expression.Constant(ItemTypeValue , typeof(int?))
....
var lambda = Expression.Lambda<Func<T, bool>>(
Expression.Equal(
Expression.Property(param, "ItemType"),
right),
param);

Categories