cast left side of expression predicate into string - c#

i am having a generic method which return an expression predicate to filter data from list.
MemberExpression member = Expression.Property(param, filter.ColumnName);
ConstantExpression constant = Expression.Constant(filter.TextToBeFiltered);
switch (filter.FilterOperation)
{
case FilterEnum.Equals:
return Expression.Equal(member, constant);
}
var res = List.Where(reqExpression).ToList();
Problem is the properties in list some of are string,int,guid etc so i want to cast left side of expression into string because i need to compare all properties with string only like a=> a.Id.tostring() == inputstringso how to perform it in my code.

Take a look at this full example:
class Program
{
class MyType
{
public int Column { get; set; }
};
public static string AsString(object obj)
{
return obj?.ToString();
}
static void Main(string[] args)
{
var param = Expression.Parameter(typeof(MyType));
//your member
MemberExpression member = Expression.Property(param, "Column");
var asString = typeof(Program).GetMethod("AsString");
var stringMember = Expression.Call(asString, Expression.Convert(member, typeof(object)));
//your value
ConstantExpression constant = Expression.Constant("23");
//your switch
var expression = Expression.Equal(stringMember, constant);
var lambda = Expression.Lambda(expression, param);
var list = new List<MyType>
{
new MyType{Column = 23},
new MyType{Column= 24}
};
var res = list.Where((Func<MyType,bool>)lambda.Compile()).ToList();
}
}
You can also use ToString method (beware nulls!) or Convert.ChangeType. Own AsString method is good for custom types and... for debugging.
Think as building code:
p => AsString((object)p.Column) == "23"

Related

Expression.Lambda getter function compile

code:
static Func<T,object> CompileGetValueExpression<T>(PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var property = Expression.Property(instance, propertyInfo);
var convert = Expression.TypeAs(property, typeof(object));
return Expression.Lambda<Func<T,object>>(convert, instance).Compile();
}
e.g
void Main()
{
var data = new Test{prop1 = 1};
var type = data.GetType();
var prop = type.GetProperties().First();
var function = CompileGetValueExpression<Test>(prop);
var result = function(data); //result:1
}
class Test{
public int prop1 { get; set; }
}
question
Is this expression function exactly equal to below method?
object GetterFunction(Test i) => i.prop1 as object;
I think they are. One evidence is if you dump expression created in CompileGetValueExpression as string, it outputs i => (i.prop1 As Object).
Try to modify the last line of CompileGetValueExpression
Expression.Lambda<Func<T, object>>(convert, instance).ToString();

How can I convert a Parameter Expression into a function?

If I have a dynamically created ParameterExpression:
class Product
{
public string Name { get; set; }
}
var propertyName = "Name";
var propertyType = typeof(Product).GetProperty(propertyName).PropertyType;
var parameterExpression = Expression.Parameter(propertyType , propertyName);
How can I covert it into a Func<Product, TPropertyType>?
I specifically want to pass this into the Where or OrderBy linq methods used by entity framework.
I'm also open to other suggestions not using Expressions, but I highly doubt it's possible.
Edit 1: Removed the where use case as Where and OrderBy will have different implementations Removed in an attempt to narrow the scope of the question.
Here is an example with generating expressions for OrderBy and Where. As Johnathon Sullinger said in comments, you must know type of property you ordering by at compile time, because it is mentioned in signature of OrderBY. However you don't have to know it for Where:
class Product
{
public string Name { get; set; }
}
static void Main(string[] args)
{
var products = new List<Product> {
new Product { Name = "ZZZ"},
new Product { Name = "AAA"}
};
var propertyName = "Name";
var ordered = products.AsQueryable().OrderBy(GetOrderExpression<string>(propertyName));
Console.WriteLine(ordered.ElementAt(0).Name);
Console.WriteLine(ordered.ElementAt(1).Name);
var filtered = products.AsQueryable().Where(GetWhereExpression(propertyName, "AAA"));
Console.WriteLine(filtered.Count());
Console.WriteLine(filtered.ElementAt(0).Name);
Console.ReadKey();
}
static Expression<Func<Product, TKey>> GetOrderExpression<TKey>(string propertyName)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var lambda = Expression.Lambda<Func<Product, TKey>>(prop, "p", new[] { prm });
return lambda;
}
static Expression<Func<Product, bool>> GetWhereExpression(string propertyName, object value)
{
var prm = Expression.Parameter(typeof(Product), "p");
var prop = Expression.Property(prm, typeof(Product), propertyName);
var equal = Expression.Equal(prop, Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(equal, "p", new[] { prm });
return lambda;
}
Hope it helps.

LambdaExpression to Expression via Extensions Method

I looked at the other SO versions of this question but it seems the casting out of a method works for others. I am not sure what I am doing wrong here. I am new to the Expression Building part of Linq.
My extensions method is as follows:
void Main()
{
var people = LoadData().AsQueryable();
var expression = people.PropertySelector<Person>("LastName");
expression.Should().BeOfType(typeof(Expression<Func<Person, object>>));
var result = people.OrderBy(expression);
}
public static class Extensions
{
public static Expression<Func<T, object>> PropertySelector<T>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, object>>)lambda;
}
}
#region
private Random rand = new Random();
// Define other methods and classes here
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public IEnumerable<Person> LoadData()
{
IList<Person> people = new List<Person>();
for (var i = 0; i < 15; i++)
{
people.Add(new Person
{
FirstName = $"FirstName {i}",
LastName = $"LastName {i}",
Age = rand.Next(1, 100)
});
}
return people;
}
#endregion
I get an exception on the return during the cast. At this point T is type Person and object is a string. My lambda.GetType() is reporting that it's of type Expression<Func<Person, string>> The exception is:
Unable to cast object of type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.String]]' to type 'System.Linq.Expressions.Expression`1[System.Func`2[UserQuery+Person,System.Object]]'.
What about my cast is incorrect? Thanks.
EDIT:
I updated with my full code that I am playing with in LinqPad. I am really just trying to figure out if there is an easy way to generate a lambda expression by passing in the property name. I was doing something like this before but I was just doing a switch on property name then using lambda syntax to create the OrderBy query dynamically.
This is strictly me trying to learn how Expression static methods can be used to achieve the same result as the example below. I am trying to mimic the below via extension method. But it doesn't have to be that way. It was just easiest to try while dinking around in LinqPad.
Expression<Func<Loan, object>> sortExpression;
switch (propertyFilter)
{
case "Age":
sortExpression = (l => l.Age);
break;
case "LastName":
sortExpression = (l => l.LastName);
break;
default:
sortExpression = (l => l.FirstName);
break;
}
var sortedLoans = loans.AsQueryable().OrderBy(sortExpression);
sortedLoans.Dump("Filtered Property Result");
Your code is creating a Func<UserQuery, String> because you're geting it's intrinsic type with
var propertyInfo = properties.Single(p => p.Name == propertyName);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
If you want to return a Func<T, object> then create a Func<T, object>, not a Func<T, (reflected property type)>, else the better solution is to use a Func<TOut, TIn> and create a totally generic function.
To keep current method signature you could do this:
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(object));
var typeAs = Expression.TypeAs(property, typeof(object));
var lambda = Expression.Lambda(funcType, typeAs, alias);
and better way is to change your method to
public static Expression<Func<T, Tout>> PropertySelector<T, Tout>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, Tout>>)lambda;
}
and call it with
var expression = people.PropertySelector<Person, string>("LastName");
I got the result I wanted. After comments from #Gusman, #IvanStoev and #PetSerAl I got it to function. I can move on with my exploring and learning again. Thank you very much. Final result was to template the Propertytype.
public static Expression<Func<T, TPropertyType>> PropertySelector<T, TPropertyType>(this IEnumerable<T> collection, string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
{
throw new ArgumentException(nameof(propertyName));
}
var properties = typeof(T).GetProperties();
if (!properties.Any(p => p.Name == propertyName))
{
throw new ObjectNotFoundException($"Property: {propertyName} not found for type [{typeof(T).Name}]");
}
var propertyInfo = properties.Single(p => p.Name == propertyName);
var alias = Expression.Parameter(typeof(T), "_");
var property = Expression.Property(alias, propertyInfo);
var funcType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(TPropertyType));
var lambda = Expression.Lambda(funcType, property, alias);
return (Expression<Func<T, TPropertyType>>)lambda;
}

Linq Expressions for Indexer Properties

var param = Expression.Parameter(typeof(Employee), "t");
MemberExpression member = Expression.Property(param, "EmployeeName");
var value = Convert.ChangeType(filterProperty.Value, member.Type);
ConstantExpression constant = Expression.Constant(value);
var body = Expression.Or(leftExpr, Expression.Equal(member, constant));
I can easily get the expressions for normal properties, but How I can get expression for indexer properties?
In Employee class I have two indexers.
class Employee
{
public string EmployeeName {get;set;}
public string this[EmployeeTypes empType]
{
get
{
return GetEmployee(empType);
}
}
public string this[int empNum]
{
get
{
return GetEmployee(empNum);
}
}
}
Use Item as property name:
var param = Expression.Parameter(typeof(Employee), "t");
MemberExpression member = Expression.Property(param, "EmployeeName");
var body = Expression.Property(param, "Item", Expression.Constant(10));
var lambda = Expression.Lambda<Func<Employee, string>>(body, param);
var compiled = lambda.Compile();
gives you the same what could be done by
Func<Employee, string> compiled = t => t[10];

Dynamic recursive lambda expressions

I want to create dynamic lambda expressions so that I can filter a list using a set of filtering parameters. This is what I have so far:
The expression is built using the methods bellow, where T is the object type of the list
public static Expression<Func<T, bool>> GetExpression<T>(IList<DynamicFilter> filters)
{
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1)
exp = GetExpression<T>(param, filters[0]);
[...]
return Expression.Lambda<Func<T, bool>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, DynamicFilter filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
[...]
return Expression.Call(member, filterMethod, constant);
}
I then call
List<Example> list = ...;
var deleg = ExpressionBuilder.GetExpression<Example>(dynFiltersList).Compile();
list = list.Where(deleg).ToList();
This works just as expected with an object that contains only simple types, but if there are complex types inside, the code doesn't work anymore. For example, let's say I have a member of custom type Field inside the Example class and Field has a string property Value. If filter.PropertyName would be 'Field0' (of type Field), the code would work just fine, but if I have 'Field0.Value' I would get an obvious error stating that there is no property named 'Field0.Value' inside class Example.
I tried modifying the expression building method, like this:
MemberExpression member = null;
if (filter.PropertyName.Contains('.'))
{
string[] props = filter.PropertyName.Split('.');
ParameterExpression param1 = Expression.Parameter(typeof(T).GetProperty(props[0]).PropertyType, "t1");
member = Expression.Property(param1, props[0]);
}
else
{
member = Expression.Property(param, filter.PropertyName);
}
but then I got a Lambda parameter not in scope error when compiling the expression. I sort of understand why I get this error, but I don't know how to make this work.
Bottom line is I need to make the expression building method work recursively when forming the MemberExpression. I ultimately need to obtain a list = list.Where(deleg).ToList(); that translates to something like this list = list.Where(obj => obj.Field0.Value == 'something').ToList();
I've just started working with expressions so I don't really know too much in this area, but any help would be appreciated.
Thanks
I realize this is a fairly old post, but I had the exact same problem and found something close in an answer that Mark Gravell posted here. I just modified it slightly to meet my needs and below is the result:
private Expression GetDeepProperty(Expression parameter, string property)
{
var props = property.Split('.');
var type = parameter.Type;
var expr = parameter;
foreach (var prop in props)
{
var pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
return expr;
}
Implementation:
var method = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}, null);
var lambdaParameter = Expression.Parameter(typeof(TEntity), "te");
var filterExpression = Expression.Lambda<Func<TEntity, bool>> (
filters.Select(filter => Expression.Call(GetDeepProperty(lambdaParameter, filter.Property),
method,
Expression.Constant(filter.Value))).
Where(exp => exp != null).
Cast<Expression>().
ToList().
Aggregate(Expression.Or), lambdaParameter);
I'm trying to address
so that I can filter a list using a set of filtering parameters
not by using ExpressionBuilder but instead by using a generic Filter class.
public class Filter<T> where T: class
{
private readonly Predicate<T> criteria;
public Filter(Predicate<T> criteria)
{
this.criteria = criteria;
}
public bool IsSatisfied(T obj)
{
return criteria(obj);
}
}
First we need to have some classes.
public class Player
{
public string Name { get; set; }
public int Level { get; set; }
public enum Sex { Male, Female, Other };
public Weapon Weapon { get; set; }
}
public class Weapon
{
public string Name { get; set; }
public int MaxDamage { get; set; }
public int Range { get; set; }
public WeaponClass Class { get; set; }
public enum WeaponClass { Sword, Club, Bow }
}
Then we need a list of objects.
var graywand = new Weapon { Name = "Graywand", MaxDamage = 42, Range = 1, Class = Weapon.WeaponClass.Sword };
var scalpel = new Weapon { Name = "Scalpel", MaxDamage = 33, Range = 1, Class = Weapon.WeaponClass.Sword };
var players = new List<Player> {
new Player { Name = "Fafhrd", Level = 19, Weapon = graywand },
new Player { Name = "Gray Mouser", Level = 19, Weapon = scalpel },
new Player { Name = "Freddy", Level = 9, Weapon = graywand },
new Player { Name = "Mouse", Level = 8, Weapon = scalpel}
};
Then let's create a couple of filters and add those to a list.
var powerfulSwords = new Filter<Player>(p => p.Weapon.MaxDamage>35);
var highLevels = new Filter<Player>(p => p.Level>15);
var filters = new List<Filter<Player>>();
filters.Add(powerfulSwords);
filters.Add(highLevels);
Finally filter the list by those filters
var highLevelAndPowerfulSwords = players.Where(p => filters.All(filter => filter.IsSatisfied(p)));
var highLevelOrPowerfulSwords = players.Where(p => filters.Any(filter => filter.IsSatisfied(p)));
Only "Fafhrd" will be in highLevelAndPowerfulSwords and highLevelOrPowerfulSwords will contain all players but "Mouse".
Have a look at ExpressionVisitor as described here: Replacing the parameter name in the Body of an Expression
You could generate an expression for each element in filter and combine them into one single expression using the methods below:
public static Expression<Func<T, K>> CombineAnd<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
{
ParameterExpression firstParameter = a.Parameters.First();
Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
return Expression.Lambda<Func<T, K>>(Expression.And(a.Body, b1.Body), firstParameter);
}
public static Expression<Func<T, K>> CombineOr<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
{
ParameterExpression firstParameter = a.Parameters.First();
Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
return Expression.Lambda<Func<T, K>>(Expression.Or(a.Body, b1.Body), firstParameter);
}

Categories