Add variable amount of conditions to expression in loop C# - 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);
}
}
}

Related

Replace custom ExpressionVisitor with ReplacingExpressionVisitor

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;

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.

Convert params args to a lambda expression in Entity Framework

I am looking to take the params string[] args and convert it to a lambda expression for Entity Framework.
Something like this...
public main(params string[] args)
{
DataContext context = new DataContext();
foreach(string arg in args)
{
//build Query
}
context.Things.Where(/*Query*/);
}
You can use dynamic linq to create string based expressions.
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
The downside is that these expression are evaluated at runtime, so you won't be able to catch errors at compile time.
Or you can use Expression trees to construct expressions .
https://msdn.microsoft.com/en-us/library/bb882637(v=vs.110).aspx
This will be a bit more code, but you will get the advantage of compile time type checks.
An excellent article is here https://www.codeproject.com/Articles/1079028/Build-Lambda-Expressions-Dynamically
Here is my solution:
public static Expression<Func<TClass, bool>> ConvertParamArgsToExpression<TClass>(string[] args)
{
Expression finalExpression = Expression.Constant(true);
var parameter = Expression.Parameter(typeof(TClass), "x");
foreach (string arg in args) {
string[] values = arg.Split('=');
PropertyInfo prop = typeof(TClass).GetProperty(values[0]);
if(prop != null)
{
Expression expression = null;
var member = Expression.Property(parameter, prop.Name);
var constant = Expression.Constant(values[1]);
expression = Expression.Equal(member, constant);
finalExpression = Expression.AndAlso(finalExpression, expression);
}
}
return (Expression.Lambda<Func<TClass, bool>>(finalExpression, parameter));
}
Usage:
Expression<Func<AdminPageObject, bool>> expression = LambdaConverter.ConvertParamArgsToExpression<AdminPageObject>(args);
if(expression != null)
{
items = items.Where(expression);
}

Evaluating complex expression tree

I have a basic rule engine that I've built in a very similar way to the route suggested here:
How to implement a rule engine?
Ive extended it based on further requirements, and now I need to evaluate complex classes eg
EvaluateRule("Transaction.IsOpen", "Equals", "true")
The code in its most basic form is:
var param = inputMessageType;
left = Expression.Property(param, memberName);
tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
return Expression.MakeBinary(tBinary, left, right);
In order to evaluate complex classes I used a method similar to here:
https://stackoverflow.com/questions/16544672/dynamically-evaluating-a-property-string-with-expressions
The problem that Im having is that when I try to evaluate the rule with a property of a class (Transaction.IsOpen), I get it with the type of the root type on the right hand side of the expression but the type of the complex object on the left hand side of the expression.
This results in the error:
System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Func`2[Transaction,System.Boolean]' and 'System.Boolean'.
How do I overcome this problem? I am no expert using Expression Trees, and many of the concepts are proving difficult to grasp when the example strays from the standard documentation.
Edit: Here is the code(Ive omitted some stuff that is environment specific so as keep focus with the problem)
public Actions EvaluateRulesFromMessage(ClientEventQueueMessage message)
{
var ruleGroups = _ruleRepository.GetRuleList();
var actions = new Actions();
foreach (var ruleGroup in ruleGroups)
{
if (message.MessageType == "UI_UPDATE")
{
// clean up json object
JObject dsPayload = (JObject.Parse(message.Payload));
var msgParams = JsonConvert.DeserializeObject<UiTransactionUpdate>(message.Payload);
msgParams.RulesCompleted = msgParams.RulesCompleted ?? new List<int>();
var conditionsMet = false;
// process the rules filtering out the rules that have already been evaluated
var filteredRules = ruleGroup.Rules.Where(item =>
!msgParams.RulesCompleted.Any(r => r.Equals(item.Id)));
foreach (var rule in filteredRules)
{
Func<UiTransactionUpdate, bool> compiledRule = CompileRule<UiTransactionUpdate>(rule, msgParams);
if (compiledRule(msgParams))
{
conditionsMet = true;
}
else
{
conditionsMet = false;
break;
}
}
if (conditionsMet)
{
actions = AddAction(message, ruleGroup);
break;
}
}
}
return actions;
}
public Func<UiTransactionUpdate, bool> CompileRule<T>(Rule r, UiTransactionUpdate msg)
{
var expression = Expression.Parameter(typeof(UiTransactionUpdate));
Expression expr = BuildExpr<UiTransactionUpdate>(r, expression, msg);
// build a lambda function UiTransactionUpdate->bool and compile it
return Expression.Lambda<Func<UiTransactionUpdate, bool>>(expr, expression).Compile();
}
static Expression Eval(object root, string propertyString, out Type tProp)
{
Type type = null;
var propertyNames = propertyString.Split('.');
ParameterExpression param = Expression.Parameter(root.GetType());
Expression property = param;
string propName = "";
foreach (var prop in propertyNames)
{
property = MemberExpression.PropertyOrField(property, prop);
type = property.Type;
propName = prop;
}
tProp = Type.GetType(type.UnderlyingSystemType.AssemblyQualifiedName);
var param2 = MemberExpression.Parameter(tProp);
var e = Expression.Lambda(property, param);
return e;
}
static Expression BuildExpr<T>(Rule r, ParameterExpression param, UiTransactionUpdate msg)
{
Expression left;
Type tProp;
string memberName = r.MemberName;
if (memberName.Contains("."))
{
left = Eval(msg, memberName, out tProp);
}
else
{
left = Expression.Property(param, memberName);
tProp = typeof(T).GetProperty(r.MemberName).PropertyType;
}
ExpressionType tBinary;
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
Expression right=null;
switch (r.ValueType) ///todo: this needs to be refactored to be type independent
{
case TargetValueType.Value:
right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
break;
}
// use a binary operation ie true/false
return Expression.MakeBinary(tBinary, left, right);
}
else
{
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(left, method, right);
}
}
The sample code does not cover all data types used in your scenario, so it's hard to tell where it breaks exactly, but from the exception System.Func'2[Transaction,System.Boolean]' and 'System.Boolean it's obvious that on left hand you have a delegate that takes in Transaction and returns bool (Func<Transaction, bool>), and on the right hand you just have bool.
It's not possible to compare Func<Transaction, bool> to bool, but it's possible to call the function and compare its result:
Func<Transaction, bool> func = ...;
bool comparand = ...;
Transaction transaction = ...;
if (func(transaction) == comparand) { ... }
What translated to expression tree:
Expression funcExpression = ... /*LambdaExpression<Func<Transaction,bool>>*/;
Expression comparandExpression = Expression.Constant(true);
Expression transactionArg = /*e.g.*/Expression.Constant(transaction);
Expression funcResultExpression = Expression.Call(funcExpression, "Invoke", null, transactionArg);
Expression equalityTestExpression = Expression.Equal(funcResultExpression, comparandExpression);

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