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
Related
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);
}
}
}
I'm trying to clean up my code a little by creating an extension method to generically handle filtering.
Here is the code I'm trying to clean.
var queryResult = (from r in dc.Retailers select r);
if (!string.IsNullOrEmpty(firstName))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(firstName.Trim(), ex.FirstName.Trim()) > 0);
if (!string.IsNullOrEmpty(lastName))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(lastName.Trim(), ex.LastName.Trim()) > 0);
if (!string.IsNullOrEmpty(companyName))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(companyName.Trim(), ex.CompanyName.Trim()) > 0);
if (!string.IsNullOrEmpty(phone))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(phone.Trim(), ex.Phone.Trim()) > 0);
if (!string.IsNullOrEmpty(email))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(email.Trim(), ex.Email.Trim()) > 0);
if (!string.IsNullOrEmpty(city))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(city.Trim(), ex.City.Trim()) > 0);
if (!string.IsNullOrEmpty(zip))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(zip.Trim(), ex.Zip.Trim()) > 0);
if (!string.IsNullOrEmpty(country))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(country.Trim(), ex.Country.Trim()) > 0);
if (!string.IsNullOrEmpty(state))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(state.Trim(), ex.State.Trim()) > 0);
It's clearly very repetitious. So I tried to create an extension method that filtered by a property using reflection. Here is the method.
public static void FilterByValue<T>(this IQueryable<T> obj, string propertyName, string propertyValue)
{
if (!string.IsNullOrEmpty(propertyValue))
{
obj =
obj.Where(
ex =>
SqlFunctions.PatIndex(propertyValue.Trim(), (string)typeof(T).GetProperty(propertyName,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).GetValue(ex)) > 0
);
}
}
and it's to be called like so:
var queryResult = (from r in dc.Retailers select r);
queryResult.FilterByValue("firstname", firstName);
But, I get an error when the linq executes, stating that "GetValue" isn't recognized in linq to entity.
So, is there any other way to clean this up, or do I have to leave it ugly?
Technically, yes, you could do it, but you'd need to construct the Expression yourself to pass to Where.
That said, rather than accepting the property as a string value you should consider instead accepting an Expression<Func<T, string>> as a parameter so that you have compile time support for verifying that the selected object is valid.
We'll start out with an expression that represents the generic portion; it'll represent a function with*two* parameters, the object and the value of the given property. We can then replace all instances of that second parameter with the property selector that we've defined in the actual method's parameters.
public static IQueryable<T> FilterByValue<T>(
this IQueryable<T> obj,
Expression<Func<T, string>> propertySelector,
string propertyValue)
{
if (!string.IsNullOrEmpty(propertyValue))
{
Expression<Func<T, string, bool>> expression =
(ex, value) => SqlFunctions.PatIndex(propertyValue.Trim(),
value.Trim()) > 0;
var newSelector = propertySelector.Body.Replace(
propertySelector.Parameters[0],
expression.Parameters[0]);
var body = expression.Body.Replace(expression.Parameters[1],
newSelector);
var lambda = Expression.Lambda<Func<T, bool>>(
body, expression.Parameters[0]);
return obj.Where(lambda);
}
else
return obj;
}
And this method uses a function to replace all instances of one expression with another in a given expression. The implementation of that is:
public class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
If you really want to accept the property name as a string then just replace the definition of newSelector with the following:
var newSelector = Expression.Property(expression.Parameters[0], propertyName);
I'm trying to build a dynamic Linq to Sql query and it's going pretty well, except for invoking the SqlMethods.Like method. My code is below and the body of the linq statement being generated looks like this:
Body = {((((log.ClientCode == "C1") OrElse
(log.ClientCode == "C2")) AndAlso
(log.Source == "S1")) AndAlso Like("Message", "%1%"))}
As you can see, it attempts to call "Like" without the SqlMethods class. Any idea what I'm doing wrong??
public IEnumerable<ILog> Get(int pageNumber, int pageCount,
List<string> clientCodes, List<string> sources, List<LogLevel> logLevels,
string messageContains, string userNameContains,
DateTime? dateStart, DateTime? dateEnd)
{
var expressions = new List<Expression>();
ParameterExpression pe = Expression.Parameter(typeof(Data.Logging.Log), "log");
if (clientCodes != null && clientCodes.Count > 0)
{
expressions.Add(CreateClientCodeExpression(pe, clientCodes));
}
if (sources != null && sources.Count > 0)
{
expressions.Add(CreateSourceExpression(pe, sources));
}
if (logLevels != null && logLevels.Count > 0)
{
expressions.Add(CreateLogLevelExpression(pe, logLevels));
}
if (!string.IsNullOrWhiteSpace(messageContains))
{
expressions.Add(CreateMessageExpression(pe, messageContains));
}
Expression exp = null;
if (expressions.Count > 0)
{
exp = expressions[0];
}
for (var i = 1; i < expressions.Count; i++)
{
exp = Expression.AndAlso(exp, expressions[i]);
}
var predicate = Expression.Lambda<Func<Data.Logging.Log, bool>>(exp, pe);
var results = DbContext.Logs.Where(predicate).ToList();
foreach (var result in results)
{
yield return ConvertDbLogToLog(result);
}
}
private Expression CreateClientCodeExpression(ParameterExpression pe, List<string> clientCodes)
{
Expression result = null;
clientCodes.ForEach(cc =>
{
MemberExpression me = Expression.Property(pe, "ClientCode");
ConstantExpression ce = Expression.Constant(cc);
if (result == null) { result = Expression.Equal(me, ce); }
else { result = Expression.OrElse(result, Expression.Equal(me, ce)); }
});
return result;
}
private Expression CreateSourceExpression(ParameterExpression pe, List<string> sources)
{
Expression result = null;
sources.ForEach(s =>
{
MemberExpression me = Expression.Property(pe, "Source");
ConstantExpression ce = Expression.Constant(s);
if (result == null) { result = Expression.Equal(me, ce); }
else { result = Expression.OrElse(result, Expression.Equal(me, ce)); }
});
return result;
}
private Expression CreateLogLevelExpression(ParameterExpression pe, List<LogLevel> logLevels)
{
Expression result = null;
logLevels.ForEach(l =>
{
MemberExpression me = Expression.Property(pe, "LogLevel");
ConstantExpression ce = Expression.Constant(l.ToString());
if (result == null) { result = Expression.Equal(me, ce); }
else { result = Expression.OrElse(result, Expression.Equal(me, ce)); }
});
return result;
}
private MethodCallExpression CreateMessageExpression(ParameterExpression pe, string message)
{
return Expression.Call(typeof(SqlMethods).GetMethod("Like", new[] { typeof(string), typeof(string) }),
Expression.Constant("Message"), Expression.Constant(string.Format("%{0}%", message)));
}
Actually, the 'Contains' operator is not always sufficient. For example, if you want to search something like this: 'first%last'. The '%' in the string will be taken literally instead of a wildcard as intended. In order to use the 'SqlMethods.Like' operator you can use the following:
public static MethodCallExpression Like(this ParameterExpression pe, string value)
{
var prop = Expression.Property(pe, pe.Name);
return Expression.Call(typeof(SqlMethods), "Like", null, prop, Expression.Constant(value));
}
You can skip the Like invocation, and use Contains, which both Linq-to-SQL and Linq-to-Entities correctly translate to a LIKE statement. You even save the quoting! (which, btw, you are doing wrong).
private MethodCallExpression CreateMessageExpression(ParameterExpression pe, string message)
{
return Expression.Call(Expression.Property(pe, "Message"), "Contains", null, Expression.Constant(message));
}
I'm trying to clean up my code a little by creating an extension method to generically handle filtering.
Here is the code I'm trying to clean.
var queryResult = (from r in dc.Retailers select r);
if (!string.IsNullOrEmpty(firstName))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(firstName.Trim(), ex.FirstName.Trim()) > 0);
if (!string.IsNullOrEmpty(lastName))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(lastName.Trim(), ex.LastName.Trim()) > 0);
if (!string.IsNullOrEmpty(companyName))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(companyName.Trim(), ex.CompanyName.Trim()) > 0);
if (!string.IsNullOrEmpty(phone))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(phone.Trim(), ex.Phone.Trim()) > 0);
if (!string.IsNullOrEmpty(email))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(email.Trim(), ex.Email.Trim()) > 0);
if (!string.IsNullOrEmpty(city))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(city.Trim(), ex.City.Trim()) > 0);
if (!string.IsNullOrEmpty(zip))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(zip.Trim(), ex.Zip.Trim()) > 0);
if (!string.IsNullOrEmpty(country))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(country.Trim(), ex.Country.Trim()) > 0);
if (!string.IsNullOrEmpty(state))
queryResult = queryResult.Where(ex => SqlFunctions.PatIndex(state.Trim(), ex.State.Trim()) > 0);
It's clearly very repetitious. So I tried to create an extension method that filtered by a property using reflection. Here is the method.
public static void FilterByValue<T>(this IQueryable<T> obj, string propertyName, string propertyValue)
{
if (!string.IsNullOrEmpty(propertyValue))
{
obj =
obj.Where(
ex =>
SqlFunctions.PatIndex(propertyValue.Trim(), (string)typeof(T).GetProperty(propertyName,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).GetValue(ex)) > 0
);
}
}
and it's to be called like so:
var queryResult = (from r in dc.Retailers select r);
queryResult.FilterByValue("firstname", firstName);
But, I get an error when the linq executes, stating that "GetValue" isn't recognized in linq to entity.
So, is there any other way to clean this up, or do I have to leave it ugly?
Technically, yes, you could do it, but you'd need to construct the Expression yourself to pass to Where.
That said, rather than accepting the property as a string value you should consider instead accepting an Expression<Func<T, string>> as a parameter so that you have compile time support for verifying that the selected object is valid.
We'll start out with an expression that represents the generic portion; it'll represent a function with*two* parameters, the object and the value of the given property. We can then replace all instances of that second parameter with the property selector that we've defined in the actual method's parameters.
public static IQueryable<T> FilterByValue<T>(
this IQueryable<T> obj,
Expression<Func<T, string>> propertySelector,
string propertyValue)
{
if (!string.IsNullOrEmpty(propertyValue))
{
Expression<Func<T, string, bool>> expression =
(ex, value) => SqlFunctions.PatIndex(propertyValue.Trim(),
value.Trim()) > 0;
var newSelector = propertySelector.Body.Replace(
propertySelector.Parameters[0],
expression.Parameters[0]);
var body = expression.Body.Replace(expression.Parameters[1],
newSelector);
var lambda = Expression.Lambda<Func<T, bool>>(
body, expression.Parameters[0]);
return obj.Where(lambda);
}
else
return obj;
}
And this method uses a function to replace all instances of one expression with another in a given expression. The implementation of that is:
public class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
If you really want to accept the property name as a string then just replace the definition of newSelector with the following:
var newSelector = Expression.Property(expression.Parameters[0], propertyName);
private System.Linq.Expressions.Expression<Func<ActionLogs, bool>> GetExpression()
{
Expression<Func<ActionLogs, bool>> expr = w => w.ID != -1;
if (ActionDate != null)
{
Expression<Func<ActionLogs, bool>> byDate = w => w.DateAction == ActionDate;
var body = Expression.AndAlso(expr.Body, byDate.Body);
expr = Expression.Lambda<Func<ActionLogs, bool>>(body, expr.Parameters[0]);
}
if (ActionType != ActionTypeEnum.Empty)
{
Expression<Func<ActionLogs, bool>> byActionType = w => w.ActionTypeID == (int)ActionType;
var body = Expression.AndAlso(expr.Body, byActionType.Body);
expr = Expression.Lambda<Func<ActionLogs, bool>>(body, expr.Parameters[0]);
}
if (!String.IsNullOrWhiteSpace(AuthorLogin))
{
Expression<Func<ActionLogs, bool>> byLogin = w => w.User.LoginName == AuthorLogin;
var body = Expression.AndAlso(expr.Body, byLogin.Body);
expr = Expression.Lambda<Func<ActionLogs, bool>>(body, expr.Parameters[0]);
}
if (!String.IsNullOrWhiteSpace(AdditionalInfo))
{
Expression<Func<ActionLogs, bool>> byAdditionalInfo = w => w.DescriptionText.Contains(AdditionalInfo);
var body = Expression.AndAlso(expr.Body, byAdditionalInfo.Body);
expr = Expression.Lambda<Func<ActionLogs, bool>>(body, expr.Parameters[0]);
}
return expr;
}
This is the function that generate my expression.
and when i do this:
System.Linq.Expressions.Expression<Func<ActionLogs, bool>> expr = GetExpression();
result = blablabla.Where(expr);
It says to me that The parameter 'w' is not in scope.
So the question is, how i can generate my expression, that depends on something i need, and paste it into LINQ to SQL query?
public Expression<Func<T, bool>> Combine<T>(Expression<Func<T, Boolean>> first, Expression<Func<T, Boolean>> second)
{
var toInvoke = Expression.Invoke(second, first.Parameters.Cast<Expression>());
return (Expression.Lambda<Func<T, Boolean>>(Expression.AndAlso(first.Body, toInvoke), first.Parameters));
}
This function was very helpful. It combined two expressions and built correct tree.
Could it be that you are trying to return an Expression.Lambda<T> and then assign it to an object that is a SON of it on its hierarchy(System.Linq.Expressions.Expression<T>)
Please take a look at http://msdn.microsoft.com/en-us/library/system.linq.expressions.lambdaexpression.aspx
Hope that helps,