C# Building an Action Expression with a code block - c#

I am tying to build an Expression tree that would represent this lambda:
Action<TFrom, TTo> map =
(from, to) =>
{
to.Property1 = (Nullable<TTo>)from.Property1;
to.Property2 = (Nullable<TTo>)from.Property2;
// ...continued for all properties
};
Essentially, I am trying to map the non-nullable properties from one class to the Nullable<T> properties of another class that share the same property name.
I have written this (incorrect) tree in my attempt to do this:
SomeObjWithOutNullable i = new SomeObjWithOutNullable(); // Not "object".. doh
SomeObjWithNullable j = new SomeObjWithNullable();
ParameterExpression p1 = Expression.Parameter(typeof(SomeObjWithOutNullable), "from");
ParameterExpression p2 = Expression.Parameter(typeof(SomeObjWithNullable), "to");
MemberExpression m1 = Expression.PropertyOrField(p1, "Property1");
MemberExpression m2 = Expression.PropertyOrField(p2, "Property1");
BinaryExpression body = Expression.Assign(m1, m2);
LambdaExpression lambda = Expression.Lambda<Action<SomeObjWithOutNullable,SomeObjWithNullable>>(body, new[] { p1,p2 });
var action = lambda.Compile();
action(i,j);
This does not compile. I get this exception when I attempt to:
Delegate 'System.Action<SomeObjWithOutNullable,SomeObjWithNullable>' has some invalid arguments
Argument 1: cannot convert from 'object' to 'SomeObjWithNullable'
Argument 2: cannot convert from 'object' to 'SomeObjWithNullable'
I know I have yet to add in the type conversion, but I cannot seem to figure out how to do even the assignment correctly.

Ok, I found the solution:
void NullPropertyConvertionAction<TSource, TTarget>(TSource source, TTarget target)
{
var sourceDictionary = typeof(TSource).GetProperties()
.ToDictionary(s =>
s.Name.
StringComparer.InvariantCultureIgnoreCase
);
ParameterExpression p1 = Expression.Parameter(typeof(TSource), "from");
ParameterExpression p2 = Expression.Parameter(typeof(TTarget), "to");
var expressionBodies = new List<BinaryExpression>();
foreach (var member in typeof(TTarget).GetProperties()
.Where(p=> p.PropertyType.IsGenericType
&& p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
if (sourceDictionary.ContainsKey(member.Name))
{
MemberExpression m1 = Expression.PropertyOrField(p1, sourceDictionary[member.Name].Name);
MemberExpression m2 = Expression.PropertyOrField(p2, member.Name);
BinaryExpression body = Expression.Assign(m2, Expression.Convert(m1, member.PropertyType));
expressionBodies.Add(body);
}
}
BlockExpression block = Expression.Block(expressionBodies.ToArray());
LambdaExpression lambda = Expression.Lambda<Action<TSource,TTarget (block, new[] { p1,p2 });
Action<TSource,TTarget> action = (Action<TSource,TTarget>)lambda.Compile();
action(source,target);
}

You've thrown away the type information when you downcast your lambda to LambdaExpression. The easiest way to solve this would be to use var:
var lambda = Expression.Lambda ...
Alternatively, use the full correct type:
Expression<Action<TSource, TTarget>> lambda = Expression.Lambda ...

Related

Creating a property selector Expression from a string

I'm trying to generate a "Property Selector" from a string.
Let me explain myself a bit with a Real life example:
We have a Person class with a Name (string) property.
I could manually create a "property selector" like this propertySelector writing:
Expression<Func<Person, string>> propertySelector = x => x.Name;
But I would like to get the same property selector with my method.
var propertySelector = CreatePropertySelectorExpression<Person, string>("Name");
What I have so far is this:
public static Expression<Func<TIn, TOut>> CreatePropertySelectorExpression<TIn, TOut>(string path)
{
Expression exp = Expression.Parameter(typeof(TIn), "x");
foreach (var property in path.Split('.'))
{
exp = Expression.PropertyOrField(exp, property);
}
return exp;
}
But... I've got and invalid cast error!
Cannot implicitly convert type 'System.Linq.Expressions.Expression' to
'System.Linq.Expressions.Expression>'. An
explicit conversion exists (are you missing a cast?)
I'm very new to Expressions and I don't know how to continue :(
Your exp only contains the body of the lambda. But you want an actual lambda function that takes a parameter of type TIn there. So you need to create a lambda using Expression.Lambda:
var param = Expression.Parameter(typeof(TIn));
var body = Expression.PropertyOrField(param, propertyName);
return Expression.Lambda<Func<TIn, TOut>>(body, param);
Note though that the expression does not really help you much. You probably want a compiled function instead:
private static Func<TIn, TOut> CreatePropertyAccessor<TIn, TOut> (string propertyName)
{
var param = Expression.Parameter(typeof(TIn));
var body = Expression.PropertyOrField(param, propertyName);
return Expression.Lambda<Func<TIn, TOut>>(body, param).Compile();
}
You can then use it like this:
var name1 = CreatePropertyAccessor<Obj, string>("Name");
var name2 = CreatePropertyAccessor<Obj, string>("Name2");
var name3 = CreatePropertyAccessor<Obj, string>("Name3");
var o = new Obj() // Obj is a type with those three properties
{
Name = "foo",
Name2 = "bar",
Name3 = "baz"
};
Console.WriteLine(name1(o)); // "foo"
Console.WriteLine(name2(o)); // "bar"
Console.WriteLine(name3(o)); // "baz"

How to Create an Expression tree for .Where(x => x.<deep property>.Select(y => y.id).Intersect(List<int>).Any())

I'm creating a method that receives a Queryable<T> source, a string with a property name/path (could be a deep property for example "TrParent.DataTypes" to achieve this x => x.TrParent.DataTypes) and Enumerable<int> which holds the values I need to intersect.
Basically I come from the need to create the following query dynamically (I mean <DT_Det_Tr> and TrParent.DataTypes being know only at runtime, in the example DT_Det_Tr is not a type it is a class):
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
Please keep in mind that the preceding query is just an example of what I need to achieve dynamically, what I really need is an expression tree that creates a predicate like the one shown above but using a dynamic type and with the deep navigation property specified within a string.
So, I'm using this function to create the expression for the deep property:
private static LambdaExpression CreateDelegateExpression<T>(out Type resultingtype, string property, string parameterName = "x")
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type, parameterName);
Expression expr = param;
foreach (string prop in property.Split('.'))
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
resultingtype = type;
return lambda;
}
And here is what I have so far for my function:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
//List of ids
var _value = Expression.Constant(value);
//Get delegate expression to the deep property and it's inner type
Type type = null;
var lambda = CreateDelegateExpression<T>(out type, property, "x");
var enumtype = type.GetGenericArguments()[0];
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
MethodInfo innermethod = typeof(Queryable).GetMethods().Where(x => x.Name == "Select").First();
//Error on next line...
var selectCall = Expression.Call(typeof(Queryable),
"Select",
new Type[] { enumtype, typeof(long) },
lambda,
propExp);
//TODO: Add rest of logic and actually filter the source
return source;
}
In the var selectCall = line I'm getting error:
No generic method 'Select' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
I've read a lot here on SO and other sites but I can't get past this part, I feel I'm going to bump into more trouble when I get to the .Intersect(List<int>).Any() part so any help on that also would be grand, thanks.
After a lot of thought, investigation and attempts I came up with a solution.
First, I made a simpler version of my goal query (the static example I used in my question), so instead of:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any()
);
I made this:
var res = dbContext.Set<DT_Det_Tr>()
.Where
(x => x.TrParent.DataTypes
.Any(y => _vals.Contains(y.Id))
);
Which is a lot easier to translate to expressions (or at least it was for me) because it omits the Select call.
I got rid of the method I was using to create the deep navigation property expression and streamlined it in my Intersect function, this was because it was doing some work I don't really need here plus I needed access to some of the variables I use inside it, then I made this:
public static IQueryable<T> Intersect<T>(this IQueryable<T> source, string property, IEnumerable<int> value)
{
var type = typeof(T);
var _value = Expression.Constant(value); //List of ids
//Declare parameter for outer lambda
ParameterExpression param = Expression.Parameter(type, "x");
//Outer Lambda
Expression expr = param;
foreach (string prop in property.Split('.')) //Dig for deep property
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
//Get deep property's type
var enumtype = type.GetGenericArguments()[0];
//Declare parameter for inner lambda
ParameterExpression tpe = Expression.Parameter(enumtype, "y");
//Inner Collection lambda logic
//Property for inner lambda
Expression propExp = Expression.Property(tpe, enumtype.GetProperty("Id"));
//Contains method call .Contains(y.Id)
var containsMethodExp = Expression.Call(typeof(Enumerable), "Contains", new[] { propExp.Type }, _value, propExp);
//Create Expression<Func<enumtype, bool>>
var innerDelegateType = typeof(Func<,>).MakeGenericType(enumtype, typeof(bool));
//Create Inner lambda y => _vals.Contains(y.Id)
var innerFunction = Expression.Lambda(innerDelegateType, containsMethodExp, tpe);
//Get Any method info
var anyMethod = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(enumtype);
//Call Any with inner function .Any(y => _vals.Contains(y.Id))
var outerFunction = Expression.Call(anyMethod, expr, innerFunction);
//Call Where
MethodCallExpression whereCallExpression = Expression.Call
(
typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(outerFunction, new ParameterExpression[] { param })
);
//Create and return query
return source.Provider.CreateQuery<T>(whereCallExpression);
}
I hope this helps anyone trying to develop a similar solution.
Working with expression trees can be very hard and frustrating at first, but it's a really powerful tool once you get the hold of it.
If you have access to the dynamic keyword from c# 4.0, you might be able to work around the problem like this:
var _vals = new List<int>();
var res = dbContext.Set<DT_Det_Tr>()
.Where(obj => { dynamic x = obj;
return x.TrParent.DataTypes
.Select(t => t.Id)
.Intersect(_vals)
.Any();
}
);
But I don't know enough about the details of the problem you want to solve to say for sure.

Having problems with scope of lambda expressions

Let's use this method that I'm trying to call as an example:
MyMethod<MyType, string>(Expression<Func<MyType, string>> expression)
If I try to dynamically build the following expression x => x.Name and pass it to the method, everything works just fine:
var pi = typeof(MyType).GetProperty("Name");
ParameterExpression pe = Expression.Parameter(typeof(MyType), "x");
MemberExpression me = Expression.Property(pe, pi);
var exp = Expression.Lambda(me, pe);
Now let's imagine that in the exact same scope, I'm trying to build this expression instead x => item.Name using the following code, obviously item being different than the input parameter x:
var item = new MyType() { Name = "My name" };
var pi = t.GetProperty("Name");
ParameterExpression pe = Expression.Parameter(typeof(MyType), "x");
ParameterExpression pe2 = Expression.Parameter(typeof(MyType), "item");
MemberExpression me = Expression.Property(pe2, pi);
var exp = Expression.Lambda(me, pe);
When I try to call MyMethod I get the following error, probably because there's no variable "item" in the scope where MyMethod is called (which is not where I build the expression):
variable 'item' of type 'MyType' referenced from scope '', but it is not defined
How can I build this expression in such a way that it won't throw this error?
Within the scope of the lambda, the item bit is a constant - it's not a variable parameter to the invocation.
So the following should be closer:
var item = new MyType() { Name = "My name" };
var pi = t.GetProperty("Name");
ParameterExpression pe = Expression.Parameter(typeof(MyType), "x");
ConstantExpression ce = Expression.Constant(typeof(MyType), item);
MemberExpression me = Expression.Property(ce, pi);
var exp = Expression.Lambda(me, pe);
The string "item" is not related in any way to the local variable item. If you want your constructed lambda expression to capture the value of the local variable item, then instead of Expression.Parameter(typeof(MyType), "item") you need Expression.Constant(item).

Creating a LINQ Expression where parameter equals object

Given a primitive value age I know how to create an expression like this:
//assuming: age is an int or some other primitive type
employee => employee.Age == age
By doing this:
var entityType = typeof(Employee);
var propertyName = "Age";
int age = 30;
var parameter = Expression.Parameter(entityType, "entity");
var lambda = Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(age)
)
, parameter);
That works fine except in scenarios where the property and constant in question are not primitive types.
How would I construct a similar expression if the comparison is between objects?
With EF I can just write:
Location location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location == location);
That also works, but if I try to create the same expression:
var entityType = typeof(Employee);
var propertyName = "Location";
var location = GetCurrentLocation();
var parameter = Expression.Parameter(entityType, "entity");
var lambda = Expression.Lambda(
Expression.Equal(
Expression.Property(parameter, propertyName),
Expression.Constant(location)
)
, parameter);
I get an error that says:
Unable to create a constant value of type 'Location'. Only primitive types or enumeration types are supported in this context.
My suspicion is that Expression.Constant() only expects primitive types, so I need to use a different expression factory method. (maype Expression.Object? - I know that doesn't exist)
Is there a way to create an expression that compares objects? Why is that EF is able to interpret it correctly if its a compiled LINQ statement, but not when it is an expression?
In addition to what has been mentioned in previous answers. A more specific solution would go as such:
public static Expression CreateExpression<T>(string propertyName, object valueToCompare)
{
// get the type of entity
var entityType = typeof(T);
// get the type of the value object
var valueType = valueToCompare.GetType();
var entityProperty = entityType.GetProperty(propertyName);
var propertyType = entityProperty.PropertyType;
// Expression: "entity"
var parameter = Expression.Parameter(entityType, "entity");
// check if the property type is a value type
// only value types work
if (propertyType.IsValueType || propertyType.Equals(typeof(string)))
{
// Expression: entity.Property == value
return Expression.Equal(
Expression.Property(parameter, entityProperty),
Expression.Constant(valueToCompare)
);
}
// if not, then use the key
else
{
// get the key property
var keyProperty = propertyType.GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Length > 0);
// Expression: entity.Property.Key == value.Key
return Expression.Equal(
Expression.Property(
Expression.Property(parameter, entityProperty),
keyProperty
),
Expression.Constant(
keyProperty.GetValue(valueToCompare),
keyProperty.PropertyType
)
);
}
}
IMPORTANT POINTS :
Make sure to check for nulls
Make sure propertyType and valueType are compatible (either they are the same type or are convertible)
Several assumptions are made here (e.g. that you do assign a KeyAttribute)
This code is not tested, so it is not exactly copy/paste ready.
Hope that helps.
You can't do that because EF doesn't know how to translate equality comparisons on Location into a SQL expression.
However, if you know what properties of Location you want to compare, you can do this with anonymous types:
var location = GetCurrentLocation();
var locationObj = new { location.LocationName, location.LocationDescription };
employees = DataContext.Employees.Where(e => new { e.Location.LocationName, e.Location.Description } == locationObj);
Of course that's equivalent to:
var location = GetCurrentLocation();
employees = DataContext.Employees.Where(e => e.Location.LocationName == location.Name &&
e.Location.Description == location.Description);
Give the code below a run. I wanted to test your assumption that e => e.Location == location is compiling into something that can be constructed with Expression.Equal, Expression.Property, and Expression.Constant.
class Program {
static void Main(string[] args) {
var location = new Location();
Expression<Func<Employee, bool>> expression = e => e.Location == location;
var untypedBody = expression.Body;
//The untyped body is a BinaryExpression
Debug.Assert(
typeof(BinaryExpression).IsAssignableFrom(untypedBody.GetType()),
"Not Expression.Equal");
var body = (BinaryExpression)untypedBody;
var untypedLeft = body.Left;
var untypedRight = body.Right;
//The untyped left expression is a MemberExpression
Debug.Assert(
typeof(MemberExpression).IsAssignableFrom(untypedLeft.GetType()),
"Not Expression.Property");
////The untyped right expression is a ConstantExpression
//Debug.Assert(
// typeof(ConstantExpression).IsAssignableFrom(untypedRight.GetType()),
// "Not Expression.Constant");
//The untyped right expression is a MemberExpression?
Debug.Assert(
typeof(MemberExpression).IsAssignableFrom(untypedRight.GetType())));
}
}
public class Employee
{
public Location Location { get; set; }
}
public class Location { }
It seems like it isn't, and its because the right expression isn't a Constant. To see this, uncomment the commented out code.
What I don't understand is why the right expression is a MemberExpression. Perhaps someone who knows the linq expression compiler can shed more light onto this then I can.
Edit: This may have to do with closure in lambdas - a class is created behind the scenes which contains the closed over variables. The location might then be a member of that class. I'm not sure about this, but it's what I suspect.
This post may shed additional light on the situation.

Building Expression Tree for string.Contains [duplicate]

This question already has answers here:
How do I create an expression tree to represent 'String.Contains("term")' in C#?
(4 answers)
Closed 9 years ago.
I'm struggling to build an expression tree so I can dynamically do filtering on some data.
I have come up with this, but it fails at the var lambda = line
foreach (var rule in request.Where.Rules)
{
var parameterExpression = Expression.Parameter(typeof(string), rule.Field);
var left = Expression.Call(parameterExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes));
var right = Expression.Constant(rule.Data.ToLower());
var method = typeof(string).GetMethod("Contains", new [] { typeof(string) });
var call = Expression.Call(left, method, right);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameterExpression);
query = query.Where(lambda);
}
The var rule has a Field (ex "Name") which I want to compare with the text in rule.Data (ex 'tom'). So if T.Name.Contains("tom"); I want the query to include the record, otherwise, not.
The var query is of type IQueryable<T>
EDIT: Finally got it working with this code:
foreach (var rule in request.Where.Rules)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, rule.Field);
var value = Expression.Constant(rule.Data);
var type = value.Type;
var containsmethod = type.GetMethod("Contains", new[] { typeof(string) });
var call = Expression.Call(property, containsmethod, value);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
query = query.Where(lambda);
}
You are almost there, but your parameter expression should be of type T, not String, you are also missing the expression that is getting the property from type T like name.
What you should roughly have is this
val -> Expression.Constant(typeof(string), rule.Field)
parameter -> Expression.Parameter(typeof(T), "p")
property -> Expression.Property(parameter, "PropertyName")
contains -> Expression.Call(property, containsmethod, val)
equals true -> Expression.True or equals, something like that
I am freehanding all of that, so it's likely somewhat different to be valid. The resulting expression should be something like this
p => p.Name.Contains(val)
If you want to create Where query, you must create lambda then call Where on query and pass lambda.
Try this:
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
MethodCallExpression expression = Expression.Call(typeof(Queryable), "Where",
new[] { typeof(T) }, query.Expression, lambda);
query = query.Provider.CreateQuery<T>(expression);
instead of
var result = Expression.IsTrue(call);
query = query.Provider.CreateQuery<T>(result);

Categories