Creating a property selector Expression from a string - c#

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"

Related

C# Building an Action Expression with a code block

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 ...

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.

How to Type Cast dynamically to string while using Contains in Dynamic LINQ?

I want to use Dynamic LINQ Query to Search with some text in all Properties in a class. i am using following function to create expression. I am passing property name and search text to the method.
In that method if the property type is String then it is working fine. if the property type is int, DateTime, GUID. then it is not working.
As we know Contains method only for array of elements or for string. I am thinking the value to property should type cast to string. So How to do it? Solution with Explanation is help full.
i Collected code from this.
public static Expression<Func<T, bool>> ContainsExp<T>(string propertyName, string contains)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(contains, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
Well, you probably know that's it's not possible to use ToString() in linq to entities.
So the following question is : how can I convert other types to string.
For numeric values, you have SqlFunctions.StringConvert, but it has only overloads for double? and decimal?
For DateTime, you may find something using SqlFunctions.StringConvert after having applied SqlFunctions.DatePart on your DateTime (which probably means at least 3 call to SqlFunctions.DatePart, for year, month, day)
For Guid, I don't think there's a way to do it directly. One way (at db level, if you use Sql Server) could be to have a Computed column. The computed column could store a varchar converted representation of your GUID. Maybe there's a better way.
Anyway, here's at least a sample which should work for integer as well as string:
public static Expression<Func<T, bool>> ContainsExp<T>(string propertyName, string contains)
{
//first, get the type of your property
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
//no change
var parameterExp = Expression.Parameter(typeof (T), "type");
Expression propertyExp = Expression.Property(parameterExp, propertyName);
//if property's type is int
if (propertyType == typeof (int))
{
//convert your Expression to a nullable double (or nullable decimal),
//so that you can use SqlFunctions.StringConvert
propertyExp = Expression.Convert(propertyExp, typeof (double?));
//get the SqlFunctions.StringConvert method for nullable double
var stringConvertMethod = typeof (SqlFunctions).GetMethod("StringConvert", new[] {typeof (double?)});
//call StringConvert on your converted expression
propertyExp = Expression.Call(stringConvertMethod , propertyExp);
}
//no change
var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var someValue = Expression.Constant(contains, typeof (string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
public static IQueryable<T> FieldsContains<T>(this IQueryable<T> query, List<string> fileds, string searchValue)
{
Expression predicate = null;
var parameterExpression = Expression.Parameter(typeof(T), "type");
foreach (string field in fileds)
{
var next = GetFieldContainsExpression<T>(parameterExpression, field, searchValue);
if (predicate == null)
{
predicate = next;
}
else
{
predicate = Expression.Or(predicate, next);
}
}
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameterExpression);
return query.Where(lambda);
}
private static Expression GetFieldContainsExpression<T>(ParameterExpression parameterExpression, string field, string value)
{
var propertyType = typeof(T).GetProperty(field).PropertyType;
Expression propertyExpression = Expression.Property(parameterExpression, field);
var filterValue = Expression.Constant(value);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
//call toString first to ignore type errors(datetime, int ...)
var toStringExpression = Expression.Call(propertyExpression, "ToString", Type.EmptyTypes);
var containsExpression = Expression.Call(toStringExpression, method, filterValue);
return containsExpression;
}

How to add an AND/OR expression to the following dynamic linq expression

T is a type that may or may not have a specific property, lets say 'City'. For the types that have a property named 'City', I would like to restrict the records such that only residents of the Gotham are returned and they are sorted.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExp));
string propertyToRestrictOn = "City";
string restrictedValue = "Gotham";
var restrictedProperty = type.GetProperty(propertyToRestrictOn);
if(null ! = restrictedProperty )
{
// TODO: What to add here so than only those records are returned that have a
// property named City and the value is 'Gotham'???
}
return source.Provider.CreateQuery<T>(resultExp);
}
if possible please name/link some helpful literature here as well just in case I have to create more complex queries i.e. mix And/OR
The code was borrowed from
How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?
I'm not quite sure if I have understood you correctly, but I think I was in the same situation a few months ago.
The posted code here was my solution.
I think you should be especially interested in line 24.
Edit:
PropertyInfo p = ... // I used reflection in my examply to get properties with a certain Attribute
var expressionParameter = Expression.Parameter(typeof(SomeClass), "lambda");
var parameter = new [] { expressionParameter };
var propertyAccess = Expression.Property(expressionParameter, p);
var nullCheck = Expression.NotEqual(propertyAccess, Expression.Constant(null, p.PropertyType));
var nullCheckLambda = Expression.Lambda<Func<SomeClass, Boolean>>(nullCheck, parameter);
var containsMethodInfo = typeof(String).GetMethod("Contains", new[] { typeof(String) });
var contains = Expression.Call(propertyAccess, containsMethodInfo, Expression.Constant("ell"));
var containsLambda = Expression.Lambda<Func<SomeClass, Boolean>>(contains, new[] { expressionParameter });
var predicate = Expression.Lambda<Func<SomeClass, Boolean>>(
// line 24
Expression.AndAlso(nullCheckLambda.Body, containsLambda.Body), parameter);
Console.WriteLine(predicate.ToString());
I think you're making this harder than you have to. In the first part of your code (the OrderBy()), you don't actually need to generate the whole query expression, just the lambda inside it.
And in the second part (the optional Where()) you can do pretty much the same thing, just add Expression.Equal() and Expression.Constant():
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
// necessary for value types to work
var cast = Expression.Convert(propertyAccess, typeof(object));
var orderByExp = Expression.Lambda<Func<T, object>>(cast, parameter);
IQueryable<T> result = source.OrderBy(orderByExp);
string propertyToRestrictOn = "City";
string restrictedValue = "Gotham";
var restrictedProperty = type.GetProperty(propertyToRestrictOn);
if (restrictedProperty != null)
{
var restrictionParameter = Expression.Parameter(type, "p");
var restrictionPropertyAccess =
Expression.MakeMemberAccess(restrictionParameter, restrictedProperty);
var restrictionEquality =
Expression.Equal(restrictionPropertyAccess,
Expression.Constant(restrictedValue));
var whereExp =
Expression.Lambda<Func<T, bool>>(restrictionEquality, restrictionParameter);
result = result.Where(whereExp);
}
return result;
}
Also, if your method is going to do more than just ordering, I think you shouldn't call it OrderBy().
You're half-way there already. You have the ordered expression already, so you're just going to use the Queryable.OrderBy expression call in a Queryable.Where expression (or vice-versa, doesn't really matter).
if(null != restrictedProperty )
{
var notEqualExp = Expression.NotEqual(parameter,
Expression.Constant(restrictedValue, typeof(string)));
resultExp = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { type },
resultExp,
Expression.Lambda(notEqualExp, parameter));
}
Haven't worked with building Expressions by hand in a while, so this is purely being done from memory. However, it should at least get you started and give you something to work with.
P.S. I would actually perform this check BEFORE the OrderBy method call. That way you end up with Queryably.Where(...).OrderBy(...) instead. But I guess if this is getting translated by the provider anyway, then shouldn't matter. However, just something I'd do to reduce any ambiguity of the generated query.

dynamic query using expression tree

I have a form in which the user will choose the following from dropdown lists:
table_name
columnName_to_sort_by
columnName_to_search_in
The user shall enter Search_text in a text box
The form shall draw data from many tables. I want to avoid writing the sort and search for every field for each of the tables. This is why I want to use expression trees. I want to build the query dynamically.
I want to write a generic method that will generate the expression tree for the select, where and orderby methods, depending on user input. I can use System.Reflection to get the Type that is being queried (all my tables are types - I am using LinqToSql).
I do not know how to form the expression trees.
Here's what I have so far:
private static List<T> GetSortedData<T>( string sortColumnName)
{
var type = typeof(T);
var property = type.GetProperty(sortColumnName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, WHAT_SHOULD_BE_HERE, Expression.Quote(orderByExp));
return (List<T>)Expression.Lambda(resultExp).Compile().DynamicInvoke();
}
How can I implement select, sort and orderby dynamically using expression trees?
What you have is close. Where you ask, "WHAT_SHOULD_BE_HERE", you are curious what expression to use to indicate the "source" parameter for OrderBy, which is usually implied from the operand when used as an extension method. What you need to do is change your sample to operate on IQueryable, and you need to accept that as an input parameter. Also, replace your WHAT_SHOULD_BE_HERE placeholder with "list.Expression" as shown below.
private static IEnumerable<T> GetSortedData<T>(IQueryable<T> list, string sortColumnName)
{
var type = typeof(T);
var property = type.GetProperty(sortColumnName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new[] { type, property.PropertyType }, list.Expression, Expression.Quote(orderByExp));
return (IEnumerable<T>)Expression.Lambda(resultExp).Compile().DynamicInvoke();
}
I tested this with the following code:
static void Main(string[] args)
{
var list = new List<Person>(new[]
{
new Person { FirstName = "John" },
new Person { FirstName = "Jane" }
}).AsQueryable();
foreach (var o in GetSortedData(list, "FirstName"))
Console.WriteLine(o.FirstName);
}
public class Person
{
public string FirstName { get; set; }
}
Which printed out:
Jane
John
I was facing the same Error with Order by.
I looked into the Call method and found that I was missing the Parameter -- list.Expression, where list is your IQuerable

Categories