C# Expression, Accessing Property - c#

I am trying to build a filter method for IQueryable Type,
I could achieve this for ex:
query = query.Filter(UsersListId, e => e.UserId);
public static IQueryable<T> Filter<T, TSearch>(this IQueryable<T> query, List<TSearch> list, Expression<Func<T, TSearch>> props)
{
if (list == null || list.Count == 0)
{
return query;
}
var propertyPath = props.Body.ToString().Replace(props.Parameters[0] + ".", string.Empty);
var containsMethod = typeof(List<TSearch>).GetMethod("Contains", new Type[] { typeof(TSearch) });
ConstantExpression constlist = Expression.Constant(list);
var param = Expression.Parameter(typeof(T), "Entity");
var newvalue = GetPropertyValue(param, propertyPath);
var body = Expression.Call(constlist, containsMethod, newvalue);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(exp);
}
public static MemberExpression GetPropertyValue(ParameterExpression parExp, string propertyPath)
{
var properties = propertyPath.Split('.').ToArray();
var value = Expression.Property(parExp, properties[0]);
for (int i = 1; i < properties.Length; i++)
{
value = Expression.Property(value, properties[i]);
}
return value;
}
this code takes the propertyPath (ex: x.User.Department.Id)
and returns the value,
The problems here:
I cannot pass a nullable object if the list isn't nullable too
I am not quite sure weather it's the right way to access properties
so my question is what is the solution for these problems?

Related

Record 'Lenses' - expression tree for with expression

Is there a way to build an expression tree for the new with operator?
I am trying to implement a 'Lens' feature for records that will only need a selector and will auto generate the mutator
My goal is to convert from a 'selector':
Expression<Func<T, TMember>> expression (ie employee => employee.Name)
To a 'mutator':
(employee, newName) => employee with { Name = newName }
I did manage to do this for the simple case above, see my answer below, however that will not work for a nested case ie:
record Employee(string Name, int Age);
record Manager(String Name, Employee Employee);
Here I want to change ie from
manager => manager.Employee.Name
to
(manager, newEmployeeName) => manager with { Employee = manager.Employee with { Name = newEmployeeName}}
Any help ?
CalcMutator method that can deal with nested properties would look something like this
static Func<T, TMember, T> CalcMutator(Expression<Func<T, TMember>> expression)
{
var typeParam = expression.Parameters.First();
var valueParam = Expression.Parameter(typeof(TMember), "v");
var variables = new List<ParameterExpression>();
var blockExpressions = new List<Expression>();
var property = (MemberExpression)expression.Body;
Expression currentValue = valueParam;
var index = 0;
while (property != null)
{
var variable = Expression.Variable(property.Expression.Type, $"v_{index}");
variables.Add(variable);
var cloneMethod = property.Expression.Type.GetMethod("<Clone>$");
if (cloneMethod is null) throw new Exception($"CalcMutatorNo Clone method on {typeof(T)}");
var cloneCall = Expression.Call(property.Expression, cloneMethod);
var assignClonedToVariable = Expression.Assign(variable, cloneCall);
var accessVariableProperty = Expression.MakeMemberAccess(variable, property.Member);
var assignVariablePropertyValue = Expression.Assign(accessVariableProperty, currentValue);
blockExpressions.Add(assignClonedToVariable);
blockExpressions.Add(assignVariablePropertyValue);
property = property.Expression as MemberExpression;
currentValue = variable;
index++;
}
// Return root object
blockExpressions.Add(currentValue);
var block = Expression.Block(variables, blockExpressions);
var assignLambda = (Expression<Func<T, TMember, T>>)Expression.Lambda(block, typeParam, valueParam);
return assignLambda.Compile();
}
Please keep in mind that Cache implemented with ImmutableDictionary is not thread safe. If you want to ensure that the cached expressions can safely be used in multi-threaded environments, it's better to use ConcurrentDictionary for the cache instead or to apply some synchronization primitives around ImmutableDictionary.
Following the lead from #JL0PD I ended up converting:
t => t.Member (ie employee => employee.Name)
into:
(t, v) => {
var c = t.<Clone>$();
c.Member = v;
return c;
}
ie:
(employee, newName) => {
var c = employee.<Clone>$();
c.Name=newName;
return c;
}
Below is a full implemetation of a record Lens including caching of delegates
Note that this does not cover nested mutators so my question above still stands
static class RecLens<T, TMember> {
public static (Func<T, TMember> Selector, Func<T, TMember, T> Mutator) Get(Expression<Func<T, TMember>> expression) {
if (!IsExpressionValid(expression.Body)) throw new Exception($"Lens Invalid expression ({expression})");
// create unique cache key, calc same key for x=>x.p and y=>y.p
var exprStr = expression.Body.ToString();
var dotPos = exprStr.IndexOf(Type.Delimiter);
var cacheKey = typeof(T).FullName + '|' + (dotPos > 0 ? exprStr.Remove(0, exprStr.IndexOf(Type.Delimiter) + 1) : "root");
if (!Cache.TryGetValue(cacheKey, out var res)) {
res = (expression.Compile(), CalcMutator(expression));
Cache = Cache.Add(cacheKey, res);
}
return res;
}
// key: "{srcType.FullName}|{member}" , ie: "Test.Organization|DevelopmentDepartment.Manager"
static ImmutableDictionary<string, (Func<T, TMember>, Func<T, TMember, T>)> Cache = ImmutableDictionary<string, (Func<T, TMember>, Func<T, TMember, T>)>.Empty;
// create delegate: (t, v) => { var c=t.<Clone>$(); c.Member = v; return c; }
static Func<T, TMember, T> CalcMutator(Expression<Func<T, TMember>> expression) {
var result = Expression.Variable(typeof(T), "c");
var typeParam = Expression.Parameter(typeof(T), "t");
var valueParam = Expression.Parameter(typeof(TMember), "v");
var cloneMethod = typeof(T).GetMethod("<Clone>$");
if (cloneMethod is null) throw new Exception($"CalcMutatorNo Clone method on {typeof(T)}");
var cloneCall = Expression.Call(typeParam, cloneMethod);
var assignResult = Expression.Assign(result, cloneCall);
var memberInfo = (expression.Body as MemberExpression)!.Member;
var resultMemberAccess = Expression.MakeMemberAccess(result, memberInfo);
var assign = Expression.Assign(resultMemberAccess, valueParam);
var block = Expression.Block(new[] { result }, assignResult, assign, result);
var assignLambda = (Expression<Func<T, TMember, T>>)Expression.Lambda(block, typeParam, valueParam);
return assignLambda.Compile();
}
// verify that expr is a member expression of its parameter
static bool IsExpressionValid(Expression expr, bool first = true) {
if (expr is ParameterExpression) return !first;
if (expr is MemberExpression memberExpr && memberExpr.Expression is object) return IsExpressionValid(memberExpr.Expression, false);
return false;
}
}
To use:
record Employee(string Name, int Age);
var (Selector, Mutator) = RecLens<Employee, string>.Get(e => e.Name);
var dave = new Employee("Dave", 30);
var name = Selector(dave); // "Dave"
var john = Mutator(dave, "John"); // Employee("John", 30)

LINQ Expression Tree - The parameter 'x' was not bound in the specified LINQ to Entities query expression

I am trying to build an expression tree dynamically to fetch data from a database.
The following codes are used for this.
Expression<Func<Client, bool>> expression = x => true;
foreach (var item in searchParams)
{
var operatorType = ExpressionType.Equal;
string propertyName = null;
object value = null;
string keyValue = item.Value;
if (item.Key == Constants.SearchParameterNames.Id)
{
int val = 0;
if (int.TryParse(keyValue, out val))
value = val;
propertyName = "ClientID";
}
else if (item.Key == Constants.SearchParameterNames.Lastupdate)
{
DateTime dateTime;
if (DateTime.TryParse(keyValue, out dateTime))
value = dateTime;
propertyName = "LastChange";
}
if (!string.IsNullOrWhiteSpace(propertyName) && value != null)
{
var exp = GetBinaryOperation<Client>(propertyName, operatorType, value);
var exp1 = Expression.And(expression.Body, exp);
expression = Expression.Lambda<Func<Client, bool>>(exp1, expression.Parameters);
}
}
var client = _clientRepository.FindBy(expression).ToList();
when _clientRepository.FindBy(expression).ToList() is executed I am getting an exception of
The parameter 'x' was not bound in the specified LINQ to Entities
query expression.
The method used to create expression:
public BinaryExpression GetBinaryOperation<T>(string propertyName, ExpressionType type, object value)
{
var parameterExpression = Expression.Parameter(typeof(T), "x");
var memberExpression = Expression.Property(parameterExpression, propertyName);
var propertyType = GetMemberType(memberExpression);
var rhs = Expression.Constant(value);
var binaryExpression = Expression.MakeBinary(type, memberExpression, rhs);
return binaryExpression;
}
When building such an expression you have to preserve the top-level parameter expression instance. When you create a new parameter expression in the GetBinaryOperation function, that will be a different instance (hence the not bound term), regardless of the fact that its name is the same "x".
Instead of creating a new parameter instance, you should pass the original LambdaExpression's "x" parameter to the GetBinaryOperation function using for example expression.Parameters[0].
All in all, you have to use the same parameter expression instance throughout the entire expression tree in this case.

Column Mapping Using Expressions and Dictionary

I'm having trouble coding an expression for IQueryable object. I can't figure out the right way to use the expression to map a string to object properties.
this is my query object and mapping dictionary:
var query = context.Industries.AsQueryable();
var columnsMap = new Dictionary<string, Expression<Func<Industry, object>>>()
{
["name"] = v => v.Name,
["isicCode"] = v => v.IsicCode.Data,
["isicCodeTitle"] = v => v.IsicCode.Title,
["isActive"] = v => v.IsActive,
};
and I'm using columnsMap dictionary for applying Orderby to my query in an extentions class:
public static IQueryable<T> ApplyOrdering<T>(this IQueryable<T> query, IQueryObject queryObj, Dictionary<string, Expression<Func<T, object>>> columnsMap)
{
if (String.IsNullOrWhiteSpace(queryObj.SortBy) || !columnsMap.ContainsKey(queryObj.SortBy))
return query;
if (queryObj.IsSortAsc)
return query.OrderBy(columnsMap[queryObj.SortBy]);
else
return query.OrderByDescending(columnsMap[queryObj.SortBy]);
}
it's ok for OrderBy but I need to do the same thing for ApplyFiltering but for filter an IQueryable object I need a different expression for where method Expression<Func<T, bool>>
public static IQueryable<T> ApplyFiltering<T>(this IQueryable<T> query, IQueryObject queryObj, Dictionary<string, Expression<Func<T, object>>> columnsMap)
{
query.Where(columnsMap['name'] == "test Name"); //this is the problem.
return query;
}
the question is how can I use my columnsMap in ApplyFiltering method? or should I change my columnsMap for that?
It is quite simple. The only problem is when you have a value type column (an int or a bool or a DateTime for example)... An Expression<Func<Industry, object>> will introduce a boxing of the field/property to object that we have to remove. This problem is absent for strings.
// isActive is a bool
Expression<Func<Industry, object>> exp = columnsMap["isActive"];
object value = true; // You can't use "true" (string) here! isActive is a bool
// Other exammple
// Expression<Func<Industry, object>> exp = columnsMap["name"];
// object value = "Foo";
var body = exp.Body;
// Remove the boxing for value types
if (body.NodeType == ExpressionType.Convert)
{
body = ((UnaryExpression)body).Operand;
}
var eq = Expression.Equal(body, Expression.Constant(value, body.Type));
var exp2 = Expression.Lambda<Func<T, bool>>(eq, exp.Parameters);
return query.Where(exp2);
Example using string value:
Expression<Func<Industry, object>> exp = columnsMap["isActive"];
string value = "true";
// Other exammple
//Expression<Func<Industry, object>> exp = columnsMap["name"];
//string value = "Foo";
var body = exp.Body;
// Remove the boxing for value types
if (body.NodeType == ExpressionType.Convert)
{
body = ((UnaryExpression)body).Operand;
}
object value2 = value;
if (value2 != null && body.Type != value2.GetType())
{
value2 = Convert.ChangeType(value2, body.Type);
}
var eq = Expression.Equal(body, Expression.Constant(value2, body.Type));
var exp2 = Expression.Lambda<Func<Industry, bool>>(eq, exp.Parameters);
return query.Where(exp2);

Lambda Expression for dynamic Object

I am trying to build a Lambda Expression for a table that has been created at run time.
The Expression is build fine but when I call Compile() method I get this error
"ParameterExpression of type 'cseval.Item' cannot be used for delegate parameter of type 'System.Object'"
this is my function
public Func<dynamic, Boolean> GetWhereExp(List<WhereCondition> SearchFieldList, dynamic item)
{
ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (WhereCondition fieldItem in SearchFieldList)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
//the name constant to match
Expression columnValue = Expression.Constant(fieldItem.Value);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e;
}
else
{
combined = Expression.And(combined, e);
}
}
}
var result = Expression.Lambda<Func<dynamic, bool>>(combined, pe);
return result.Compile();
}
I've changed dynamic to generics, this code works for me:
public Func<T, Boolean> GetWhereExp<T>(List<WhereCondition> SearchFieldList, T item)
{
var pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (var fieldItem in SearchFieldList)
{
var columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
var columnValue = Expression.Constant(fieldItem.Value);
var e1 = Expression.Equal(columnNameProperty, columnValue);
combined = combined == null ? e1 : Expression.And(combined, e1);
}
}
var result = Expression.Lambda<Func<T, bool>>(combined, pe);
return result.Compile();
}
Small remark: your method returns function, not an expression, so the name 'GetWhereExp' is slightly incorrect. If you want to return function, imho, it's better to use reflection.
UPD: I use this code to test:
var expressions = new List<WhereCondition>
{
new WhereCondition("Column1", "xxx"),
new WhereCondition("Column2", "yyy"),
};
var item = new
{
Column1 = "xxx",
Column2 = "yyy"
};
var func = LinqExpr.GetWhereExp(expressions, (dynamic)item);
Console.WriteLine(new[] {item}.Count(a => func(a)));

Dynamic expression tree to filter on nested collection properties

I'm using Entity Framework and building queries using navigation properties dynamically.
For most of my use cases, the following works fine:
private static MethodCallExpression GetNavigationPropertyExpression<T>(string propertyName, int test,
ParameterExpression parameter, string subParameter)
{
var navigationPropertyCollection = Expression.Property(parameter, propertyName);
var childType = navigationPropertyCollection.Type.GetGenericArguments()[0];
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(childType);
var aclAttribute = GetAclAttribute(typeof(T), propertyName);
var childProperty = aclAttribute.ChildProperty;
var propertyCollectionGenericArg = childType;
var serviceLocationsParam = Expression.Parameter(propertyCollectionGenericArg, subParameter);
var left = Expression.Property(serviceLocationsParam, childProperty);
var right = Expression.Constant(test, typeof(int));
var isEqual = Expression.Equal(left, right);
var subLambda = Expression.Lambda(isEqual, serviceLocationsParam);
var resultExpression = Expression.Call(anyMethod, navigationPropertyCollection, subLambda);
return resultExpression;
}
I use a custom AclAttribute class assigned to properties via metadata type and partial classes. For navigation properties, a ChildProperty is provided so that the expression builder knows to look deeper for the desired property.
For example: the table Services references another table called ServiceLocations. I need the LocationId value(s) from the ServiceLocations reference. This part works fine.
My problem is when there is more than 1 nested property to go through. Another example: the table ServiceCategories references Services which, again, references ServiceLocations. Like before, I need the LocationId value(s) from ServiceLocations.
I've done this manually by using two "Any" methods, combining, and returning the resulting expression, but there will be times where navigation properties won't be collections, or a child of a navigation property may be a collection. For those cases, I need some kind of recursive option. I've been trying for a while now, and coming up short.
Since I mentioned it, here's the test method I put together for my second example. This works, but I'm violating DRY. (Note: I didn't name the tables):
private static MethodCallExpression GetNestedNavigationPropertyExpression(int test, ParameterExpression rootParameter)
{
var servicesProperty = Expression.Property(rootParameter, "tblServices");
var servicesParameter = Expression.Parameter(servicesProperty.Type.GetGenericArguments()[0], "ss");
var serviceLocationsProperty = Expression.Property(servicesParameter, "tblServiceLocations");
var serviceLocationsParameter = Expression.Parameter(serviceLocationsProperty.Type.GetGenericArguments()[0], "s");
var servicesAnyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(servicesProperty.Type.GetGenericArguments()[0]);
var serviceLocationsAnyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(serviceLocationsProperty.Type.GetGenericArguments()[0]);
var aclAttribute = GetAclAttribute(typeof(tblService), "tblServiceLocations");
var left = Expression.Property(serviceLocationsParameter, aclAttribute.ChildProperty);
var right = Expression.Constant(test, typeof(int));
var isEqual = Expression.Equal(left, right);
var subLambda = Expression.Lambda(isEqual, serviceLocationsParameter);
var endExpression = Expression.Call(serviceLocationsAnyMethod, serviceLocationsProperty, subLambda);
var intermediaryLamba = Expression.Lambda(endExpression, servicesParameter);
var resultExpression = Expression.Call(servicesAnyMethod, servicesProperty, intermediaryLamba);
return resultExpression;
}
With this it should be possible to build your queries:
public static Expression GetNavigationPropertyExpression(Expression parameter, int test, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, childType.Name);
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, test, innerProperties);
if (isCollection)
{
//build methodexpressioncall
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
navigationPropertyPredicate = Expression.Call(anyMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
//Formerly from ACLAttribute
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(test, typeof(int));
navigationPropertyPredicate = Expression.Equal(left, right);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
Call it like:
var parameter = Expression.Parameter(typeof(A), "A");
var expression = ExpressionBuilder.GetNavigationPropertyExpression(parameter, 8,"CollectionOfB", "CollectionOfC", "ID");

Categories