Replace custom ExpressionVisitor with ReplacingExpressionVisitor - c#

I have the code below working.
But instead of using the custom ParameterReplacer class like I am, I would prefer if I could eliminate this class and use ReplacingExpressionVisitor instead. But I can't seem to find the right syntax where I can get the existing parameter so that I can replace it with parameter.
GetDateExpression
protected readonly Expression<Func<T, DateTime>> GetDateExpression;
ParameterReplacer
internal class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression Parameter;
internal ParameterReplacer(ParameterExpression parameter)
{
Parameter = parameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(Parameter);
}
}
Main Code
Expression expression;
Expression startExpression, endExpression;
// Build sub expressions
var parameter = Expression.Parameter(typeof(T));
startExpression = startDate.HasValue ?
Expression.GreaterThanOrEqual(GetNullableDateExpression.Body, Expression.Constant(startDate, typeof(DateTime?))) :
null;
endExpression = endDate.HasValue ?
Expression.LessThanOrEqual(GetNullableDateExpression.Body, Expression.Constant(endDate, typeof(DateTime?))) :
null;
// Build main expression
if (startExpression != null && endExpression != null)
expression = Expression.AndAlso(startExpression, endExpression);
else if (startExpression != null)
expression = startExpression;
else
expression = endExpression;
// TODO: Change to use ReplacingExpressionVisitor instead
// Use our real parameter
expression = new ParameterReplacer(parameter)
.Visit(expression);
// Modify query
return query.Where(Expression.Lambda<Func<T, bool>>(expression, parameter));

Actually better to replace parameter before combining:
Expression expression;
Expression startExpression, endExpression;
// Build sub expressions
var parameter = Expression.Parameter(typeof(T));
var nullableExpressionBody = ReplacingExpressionVisitor.Replace(GetNullableDateExpression.Body, GetNullableDateExpression.Parameters[0], parameter);
startExpression = startDate.HasValue ?
Expression.GreaterThanOrEqual(nullableExpressionBody, Expression.Constant(startDate, typeof(DateTime?))) :
null;
endExpression = endDate.HasValue ?
Expression.LessThanOrEqual(nullableExpressionBody, Expression.Constant(endDate, typeof(DateTime?))) :
null;
// Build main expression
if (startExpression != null && endExpression != null)
expression = Expression.AndAlso(startExpression, endExpression);
else if (startExpression != null)
expression = startExpression;
else
expression = endExpression;
// Modify query
return query.Where(Expression.Lambda<Func<T, bool>>(expression, parameter));
Also if you reuse parameter from GetNullableDateExpression lambda, replacing is not needed.
// Build sub expressions
var parameter = GetNullableDateExpression.Parameters[0];
startExpression = startDate.HasValue ?
Expression.GreaterThanOrEqual(GetNullableDateExpression.Body, Expression.Constant(startDate, typeof(DateTime?))) :
null;
endExpression = endDate.HasValue ?
Expression.LessThanOrEqual(GetNullableDateExpression.Body, Expression.Constant(endDate, typeof(DateTime?))) :
null;

Related

Add variable amount of conditions to expression in loop C#

var row = parser.ReadFields();
Expression<Func<DataRow, bool>> expression = null;
foreach (var pairToCheck in mappingDictionary)
{
Expression<Func<DataRow, bool>> newCondition = r => r[pairToCheck.Value].ToString() == row[pairToCheck.Key];
if (expression == null)
expression = newCondition;
else
expression = Expression.And(expression, newCondition.Body); // Compile error, I can't do this apparently
}
var recordFound = dt.AsEnumerable().Where(expression.Compile()).Count() > 0;
See code above, I'm trying to find a record in a DataTable (var dt), but the amount of conditions that this record has to satisfy, is variable. I tried to add conditions with Expression.And, but it turns the expression into type BinaryExpression and I can no longer turn it back to the original Expression<Func<DataRow, bool>>. What am I missing here? Is my approach at least correct?
You need to have a little bit more complicated handling. Something like this:
Expression expression = null;
var parameter = Expression.Parameter(typeof(DataRow)); // create a parameter for your future lambda
foreach (var pairToCheck in mappingDictionary)
{
Expression<Func<DataRow, bool>> newCondition = r => r[pairToCheck.Value].ToString() == row[pairToCheck.Key];
// build new body replacing parameter to correct one
var newBody = newCondition.Body.ReplaceParameter(newCondition.Parameters.First(), parameter);
if (expression == null)
expression = newBody;
else
expression = Expression.AndAlso(expression, newBody); // use AndAlso, And is a bitwise AND operation
}
var result = Expression.Lambda<Func<DataRow, bool>>(expression, parameter); // construct lambda
var recordFound = dt.AsEnumerable().Where(result.Compile()).Count() > 0;
And the ReplaceParameter method comes form:
public static class ExpressionExt
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacingVisitor { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacingVisitor : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}

String expression to Linq Where

In my project, I want the user to be able to supply an argument in the shape of: "Fieldname=Value"; I will then check the arguments supplied, and check if the Fieldname is a valid property in a Model; then, I need to have a Linq.Where() query that dynamically selects the required Fieldname for the filtering:
start.exe -Filter "TestField=ThisValue"
should translate to:
List<Mutations>.Where(s => s.{TestField} == "ThisValue" ).FirstOrDefault
the problem is that I do not know how I convert a string, or a name of a property, to the s.{TestField} part..
You can use the Reflection and Expression APIs for this. To start, I am going to assume that you are actually using properties and not fields (you are using properties, right?)
var type = typeof(Mutations);
var member = type.GetProperty("TestProperty");
if (member == null)
throw new Exception("property does not exist");
var p1 = Expression.Parameter(type, "s");
var equal = Expression.Equal(
Expression.Property(p1, member),
Expression.Constant("Test Value", member.PropertyType)
);
var lambda = Expression.Lambda<Func<Mutations, bool>>(equal, p1);
var result = list.AsQueryable().FirstOrDefault(lambda);
If you are actually using public fields (why?!) you can make the following modifications GetProperty->GetField, Expression.Property->Expression.Field and member.PropertyType->member.FieldType. Use caution though; some ORMs only work with properties and thus would reject the otherwise valid Expression.
We can take the above and turn it into a reusable, generic method that returns an Expression:
using System.Linq.Expressions;
public static class ExpressionHelpers {
public static Expression CreateWhere<T>(string propertyName, string targetValue) {
var type = typeof(T);
var member = type.GetProperty(propertyName) ?? throw new Exception("Property does not exist");
var p1 = Expression.Parameter(type, "s");
var equal = Expression.Equal(
Expression.Property(p1, member),
Expression.Constant(targetValue, member.PropertyType)
);
return Expression.Lambda<Func<T, bool>>(equal, p1);
}
}
Calling this method might look like:
public static void SomeMethod() {
var list = new List<Mutations> { /* ... */ };
Expression clause = ExpressionHelpers.CreateWhere<Mutations>("TestProperty", "TestValue");
var result = list.AsQueryable().FirstOrDefault(clause);
if (result != null)
Console.WriteLine("Result = {0}", result);
}
Note that this doesn't do any sort of validation of the data types--it assumes the property is the same type as the input, which is currently string. If you need to deal with numbers or dates or what have you, you'll need to switch on the data type and provide the appropriate parsed data to the constant:
public static Expression CreateWhere<T>(string propertyName, string targetValue) {
var type = typeof(T);
var member = type.GetProperty(propertyName) ?? throw new Exception("Property does not exist");
var propType = member.PropertyType;
if ((propType.IsClass && propType != typeof(string)) || propType.IsInterface)
throw new Exception("Interfaces and Class Types are not supported");
var p1 = Expression.Parameter(type, "s");
Expression target = null;
if (propType == typeof(string))
target = Expression.Constant(targetValue, typeof(string));
else if (propType == typeof(int) && int.TryParse(targetValue, out var intVal))
target = Expression.Constant(intVal, typeof(int));
else if (propType == typeof(long) && long.TryParse(targetValue, out var longVal))
target = Expression.Constant(longVal, typeof(long));
else if (propType == typeof(DateTime) && DateTime.TryParse(targetValue, out var dateVal))
target = Expression.Constant(dateVal, typeof(DateTime));
else
throw new Exception("Target property type is not supported or value could not be parsed");
var equal = Expression.Equal(
Expression.Property(p1, member),
target
);
return Expression.Lambda<Func<T, bool>>(equal, p1);
}
As you can see this starts to get fairly complex with the more types you want to support. Also note that if you are using this without an ORM (just LINQ on a list) you are probably going to want add some support for case-[in]sensitive string comparisons. That can be delegated to a string.Equals call that could look something like this:
bool ignoreCase = true; // maybe a method parameter?
var prop = Expression.Property(p1, member);
Expression equal = null;
if (propType != typeof(string))
{
equal = Expression.Equal(prop, target);
}
else
{
var compareType = ignoreCase
? StringComparison.OrdinalIgnoreCase
: StringComparison.Ordinal;
var compareConst = Expression.Constant(compareType, typeof(StringComparison));
equal = Expression.Call(
typeof(string),
nameof(string.Equals),
new[] { typeof(string), typeof(string), typeof(StringComparison) },
prop,
target,
compareConst
);
}
return Expression.Lambda<Func<T, bool>>(equal, p1);
Note that depending on their support this may or may not work with an ORM (and may not be necessary since many databases are insensitive comparisons by default). The above also does not handle Nullable<T> (ie. int?) which adds its own set of complexities.

How to append expressions in linq?

Net core application. I have a generic repository pattern implemented. I am trying to implement some filtering functionality. I have the below code.
var param = Expression.Parameter(typeof(SiteAssessmentRequest), "x");
Expression<Func<SiteAssessmentRequest, bool>> query;
query = x => x.CreatedBy == request.Userid || x.AssignedTo == request.Userid;
Expression body = Expression.Invoke(query, param);
if (request.Client != null && request.Client.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Client.Contains(x.Client);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.CountryId != null && request.CountryId.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.CountryId.Contains(x.CountryId);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.SiteName != null && request.SiteName.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.SiteName.Contains(x.SiteName);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
if (request.Status != null && request.Status.Length != 0)
{
Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Status.Contains(x.Status);
body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
}
var lambda = Expression.Lambda<Func<SiteAssessmentRequest, bool>>(body, param);
var siteAssessmentRequest = await _siteAssessmentRequestRepository.GetAsync(lambda, null, x => x.Country).ConfigureAwait(false);
In the above code when I pass more than one parameter, for example, request. Status and request.SiteName I want to filter based on status and Sitename. When I see the query only one parameter appended in the query
{x => (Invoke(x => (Not(IsNullOrEmpty(x.CreatedBy)) AndAlso Not(IsNullOrWhiteSpace(x.CreatedBy))), x)
AndAlso Invoke(x => value(Site.V1.Implementation.GetSARByFilterAr+<>c__DisplayClass12_0)
.request.Status.Contains(x.Status), x))}
After searching so much I got below code
public static class ExpressionCombiner
{
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> exp, Expression<Func<T, bool>> newExp)
{
// get the visitor
var visitor = new ParameterUpdateVisitor(newExp.Parameters.First(), exp.Parameters.First());
// replace the parameter in the expression just created
newExp = visitor.Visit(newExp) as Expression<Func<T, bool>>;
// now you can and together the two expressions
var binExp = Expression.And(exp.Body, newExp.Body);
// and return a new lambda, that will do what you want. NOTE that the binExp has reference only to te newExp.Parameters[0] (there is only 1) parameter, and no other
return Expression.Lambda<Func<T, bool>>(binExp, newExp.Parameters);
}
class ParameterUpdateVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (object.ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
}
But I am struggling to make it to work above code.
In the above query, I see only status but not site name. So I want to include multiple expressions. can someone help me regarding this? Any help would be greatly appreciated. Thanks
Let me explain core concept of LambdaExpression. LambdaExpression has 0..N parameters and Body. Body of LambdaExpression is Expression which actually we want to reuse.
Basic mistake is trying to combine lambda expressions (changed parameter names because it is how ExpressionTree works - it compares parameters by reference, not by name):
Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
// wrong
var resultExpression = Expression.AndAlso(lambda1, lambda2);
Schematically previous wrong sample should looks like (even it will crash)
(x1 => x1.Id == 10) && (x2 => x2.Value == 20)
What we have - not desired expression but combination of "functions".
So let reuse body of LambdaExpression
// not complete
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
Result is more acceptable but still needs corrections:
(x1.Id == 10) && (x2.Value == 20)
Why we need correction? Because we are trying to build the following LambdaExpression
var param = Expression.Parameter(typeof(some), "e");
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
// still wrong
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param)
Result of newPredicate should looks like
e => (x1.Id == 10) && (x2.Value == 20)
As you can see we left in body parameters from previous two lambdas, which is wrong and we have to replace them with new parameter param ("e") and better to do that before combination.
I'm using my implementation of replacer, which just returns desired body.
So, let's write correct lambda!
Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
var param = Expression.Parameter(typeof(Some), "e");
var newBody = Expression.AndAlso(
ExpressionReplacer.GetBody(lambda1, param),
ExpressionReplacer.GetBody(lambda2, param));
// hurray!
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param);
var query = query.Where(newPredicate);
After theses steps you will have desired result:
e => (e.Id == 10) && (e.Value == 20)
And ExpressionReplacer implementation
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Zip(lambda.Parameters, toReplace, (f, s) => Tuple.Create(f, s))
.ToDictionary(e => (Expression)e.Item1, e => e.Item2)).Visit(lambda.Body);
}
}
If you plan to work with ExpressionTree closer, I suggest to install this VS extension, which should simplify your life: Readable Expressions

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.

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)));

Categories