List<object>.Contains Expression Tree - c#

I would like to build an Expression that would equate to expected...
Expression<Func<ReferencedEntity, bool>> expected = (ReferencedEntity referencedEntity) => foreignKeys.Contains(referencedEntity.Id);
Expression<Func<ReferencedEntity, bool>> actual;
foreignKeys type is a List<object>
Here is what I have so far and I think it would use Expression.Call() method but not sure exactly how to go about it.
ParameterExpression entityParameter = Expression.Parameter(typeof(TReferencedEntity), "referencedEntity");
MemberExpression memberExpression = Expression.Property(entityParameter, "Id");
Expression convertExpression = Expression.Convert(memberExpression, typeof(object)); //This is becuase the memberExpression for Id returns a int.
//Expression containsExpression = Expression.Call(????
//actual = Expression.Lambda<Func<TReferencedEntity, bool>>(????, entityParameter);
Thanks for you help.

Here is the solution I couldn't have done it without Samuel's suggestion though...
/// <summary>
///
/// </summary>
/// <param name="foreignKeys"></param>
/// <returns></returns>
private Expression<Func<TReferencedEntity, bool>> BuildForeignKeysContainsPredicate(List<object> foreignKeys, string primaryKey)
{
Expression<Func<TReferencedEntity, bool>> result = default(Expression<Func<TReferencedEntity, bool>>);
try
{
ParameterExpression entityParameter = Expression.Parameter(typeof(TReferencedEntity), "referencedEntity");
ConstantExpression foreignKeysParameter = Expression.Constant(foreignKeys, typeof(List<object>));
MemberExpression memberExpression = Expression.Property(entityParameter, primaryKey);
Expression convertExpression = Expression.Convert(memberExpression, typeof(object));
MethodCallExpression containsExpression = Expression.Call(foreignKeysParameter
, "Contains", new Type[] { }, convertExpression);
result = Expression.Lambda<Func<TReferencedEntity, bool>>(containsExpression, entityParameter);
}
catch (Exception ex)
{
throw ex;
}
return result;
}

I don't know the solution, but I know how you could get it. Create a dummy function that takes in an Expression<Func<ReferencedEntity, bool>> and pass it your lambda. And using a debugger you can examine how the compiler created the expression for you.

Related

How to create a reusable 'Contains' expression for EF Core

Problem
I need to execute a partial text search, alongside other filters, via a generic repository using expressions.
State of current code
I have a generic method that returns paged results from my database (via a common repository layer).
In the following working example;
PagedRequest contains the current pagesize and page number, and is used during respective Skip / Take operations.
PagedResult contains a collection of the results, along with the total number of records.
public Task<PagedResult<Person>> GetPeopleAsync(PersonSearchParams searchParams,
PagedRequest pagedRequest = null)
{
ParameterExpression argParam = Expression.Parameter(typeof(Locum), "locum");
// start with a "true" expression so we have an expression to "AndAlso" with
var alwaysTrue = Expression.Constant(true);
var expr = Expression.Equal(alwaysTrue, alwaysTrue);
if (searchParams != null)
{
BinaryExpression propExpr;
if (searchParams.DateOfBirth.HasValue)
{
propExpr = GetExpression(searchParams.DateStart,
nameof(Incident.IncidentDate),
argParam,
ExpressionType.GreaterThanOrEqual);
expr = Expression.AndAlso(expr, propExpr);
}
if (searchParams.DateOfDeath.HasValue)
{
propExpr = GetExpression(searchParams.DateEnd,
nameof(Incident.IncidentDate),
argParam,
ExpressionType.LessThanOrEqual);
expr = Expression.AndAlso(expr, propExpr);
}
if (searchParams.BranchId.HasValue && searchParams.BranchId.Value != 0)
{
propExpr = GetExpression(searchParams.BranchId,
nameof(Incident.BranchId), argParam);
expr = Expression.AndAlso(expr, propExpr);
}
}
var lambda = Expression.Lambda<Func<Locum, bool>>(expr, argParam);
return _unitOfWork.Repository.GetAsync(filter: lambda, pagedRequest: pagedRequest);
}
This is using my static GetExpression method for Expression.Equal, Expression.GreaterThanOrEqual and Expression.LessThanOrEqual queries as follows;
private static BinaryExpression GetExpression<TValue>(TValue value,
string propName, ParameterExpression argParam, ExpressionType? exprType = null)
{
BinaryExpression propExpr;
var prop = Expression.Property(argParam, propName);
var valueConst = Expression.Constant(value, typeof(TValue));
switch (exprType)
{
case ExpressionType.GreaterThanOrEqual:
propExpr = Expression.GreaterThanOrEqual(prop, valueConst);
break;
case ExpressionType.LessThanOrEqual:
propExpr = Expression.LessThanOrEqual(prop, valueConst);
break;
case ExpressionType.Equal:
default:// assume equality
propExpr = Expression.Equal(prop, valueConst);
break;
}
return propExpr;
}
NOTE: this code is working correctly.
Problem
Using example from other SO answers I have tried the following;
Expressions
I have tried getting the contains via an Expression;
static Expression<Func<bool>> GetContainsExpression<T>(string propertyName,
string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<bool>>(containsMethodExp);
}
This has to be converted to a BinaryExpression so it can be added to the expression tree using AndAlso. I've tried to compare the Expression with a true value, but this isn't working
if (searchParams.FirstName.IsNotNullOrWhiteSpace())
{
var propExpr = GetContainsExpression<Locum>(nameof(Locum.Firstname),
searchParams.FirstName);
var binExpr = Expression.MakeBinary(ExpressionType.Equal, propExpr, propExpr);
expr = Expression.AndAlso(expr, binExpr);
}
MethodCallExpression
I also tried returning the MethodCallExpression (instead of the Lambda above), using the following;
static MethodCallExpression GetContainsMethodCallExpression<T>(string propertyName,
string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return containsMethodExp;
}
I used this as follows;
if (searchParams.FirstName.IsNotNullOrWhiteSpace())
{
var propExpr = GetContainsMethodCallExpression<Person>(nameof(Person.FirstName),
searchParams.FirstName);
var binExpr = Expression.MakeBinary(ExpressionType.Equal, propExpr, alwaysTrue);
expr = Expression.AndAlso(expr, binExpr);
}
Exceptions
These expression are passed to a generic method that pages information out of the database, and the exceptions are thrown during the first execution of the query when I Count the total matching number of record on the constructed query.
System.InvalidOperationException: 'The LINQ expression 'DbSet()
.Where(p => True && p.FirstName.Contains("123") == True)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable ', 'AsAsyncEnumerable ', 'ToList ', or 'ToListAsync '. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
This exception is thrown on a Count method I am using in my paging code. This code is already working without any filters, and with the ExpressionType filters described at the top, so I haven't included this code as I don't believe it is relevant.
pagedResult.RowCount = query.Count();
This has to be converted to a BinaryExpression so it can be added to the expression tree using AndAlso
Negative. There is no requirement Expression.AndAlso (or Expression.OrElse) operands to be binary expressions (it would have been strange like requiring left or right operand of && or || to be always comparison operators). The only requirement is them to be bool returning expressions, hence call to string Contains is a perfectly valid operand expression.
So start by changing the type of the inner local variable from BinaryExpression to Expression:
if (searchParams != null)
{
Expression propExpr;
// ...
}
The same btw applies for the initial expression - you don't need true == true, simple
Expression expr = Expression.Constant(true); would do the same.
Now you could emit method call to string.Contains in a separate method similar to the other that you've posted (passing the ParameterExpression and building property selector expression) or inline similar to:
if (searchParams.FirstName.IsNotNullOrWhiteSpace())
{
var propExpr = Expression.Property(argParam, nameof(Person.FirstName));
var valueExpr = Expression.Constant(searchParams.FirstName);
var containsExpr = Expression.Call(
propExpr, nameof(string.Contains), Type.EmptyTypes, valueExpr);
expr = Expression.AndAlso(expr, containsExpr);
}

How to manually build Expression which will return always true?

I tried to create Expression, but failed.
I want to build something like Expression<Func<typeof(type), bool>> expression = _ => true;
My attempt:
private static Expression GetTrueExpression(Type type)
{
LabelTarget returnTarget = Expression.Label(typeof(bool));
ParameterExpression parameter = Expression.Parameter(type, "x");
var resultExpression =
Expression.Return(returnTarget, Expression.Constant(true), typeof(bool));
var delegateType = typeof(Func<,>).MakeGenericType(type, typeof(bool));
return Expression.Lambda(delegateType, resultExpression, parameter); ;
}
Usage:
var predicate = Expression.Lambda(GetTrueExpression(typeof(bool))).Compile();
But I am getting the error: Cannot jump to undefined label ''
As simple as that:
private static Expression GetTrueExpression(Type type)
{
return Expression.Lambda(Expression.Constant(true), Expression.Parameter(type, "_"));
}
I had to face the same issue, and got this code.
It seems to be clean and simple.
Expression.IsTrue(Expression.Constant(true))

Dynamic lambda expression for array property filter

As per requirement I want to create a dynamic lambda expression using C#.
For example I want to generate the dynamic query like
Employee. Address[1].City
How can I do this? Please note that the property is a dynamic one.
I have tried this code
var item = Expression.Parameter(typeof(Employee), "item");
Expression prop = Expression.Property(item, "Address", new Expression[] { Expression.Constant[1] });
prop = Expression.Property(prop, "City");
var propValue = Expression.Constant(constraintItem.State);
var expression = Expression.Equal(prop, propValue);
var lambda = Expression.Lambda<Func<Line, bool>>(expression, item);
But it did not work.
Any help would be appreciated.
Thanks.
You "dynamic query" expression (which is not really a query, it's a simple MemberExpression) can be produced as follows:
ParameterExpression param = Expression.Parameter(typeof(Employee), "item");
MemberExpression address = Expression.Property(param, "Address");
BinaryExpression indexedAddress = Expression.ArrayIndex(address, Expression.Constant(1));
MemberExpression city = Expression.Property(indexedAddress, "City"); // Assuming "City" is a string.
// This will give us: item => item.Address[1].City
Expression<Func<Employee, string>> memberAccessLambda = Expression.Lambda<Func<Employee, string>>(city, param);
If you want an actual predicate to use as part of your query, you just wrap the MemberExpression with a relevant compare expression, i.e.
BinaryExpression eq = Expression.Equal(city, Expression.Constant("New York"));
// This will give us: item => item.Address[1].City == "New York"
Expression<Func<Employee, bool>> predicateLambda = Expression.Lambda<Func<Employee, bool>>(eq, param);
In terms of your code: not sure why you're creating a lambda where the delegate type is Func<Line, bool> when the input is clearly expected to be Employee. Parameter type must always match the delegate signature.
EDIT
Non-array indexer access example:
ParameterExpression param = Expression.Parameter(typeof(Employee), "item");
MemberExpression address = Expression.Property(param, "Address");
IndexExpression indexedAddress = Expression.MakeIndex(
address,
indexer: typeof(List<string>).GetProperty("Item", returnType: typeof(string), types: new[] { typeof(int) }),
arguments: new[] { Expression.Constant(1) }
);
// Produces item => item.Address[1].
Expression<Func<Employee, string>> lambda = Expression.Lambda<Func<Employee, string>>(indexedAddress, param);
// Predicate (item => item.Address[1] == "Place"):
BinaryExpression eq = Expression.Equal(indexedAddress, Expression.Constant("Place"));
Expression<Func<Employee, bool>> predicateLambda = Expression.Lambda<Func<Employee, bool>>(eq, param);

Custom lambda expression not being applied

I have a method that is designed to check if a List of strings is contained within a List of column names in a table. Here is the method:
public static IQueryable<T> ContainsQuery<T>(IQueryable<T> query, IEnumerable<string> propertyNames, IEnumerable<string> propertyValues)
{
ParameterExpression entity = Expression.Parameter(typeof(T), "entity");
MethodInfo containsInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
MethodInfo toLowerInfo = typeof(string).GetMethod("ToLower", new Type[] {});
BinaryExpression masterExpression = Expression.Or(Expression.Constant(true, typeof(Boolean)), Expression.Constant(true, typeof(Boolean)));
ConstantExpression negativeOne = Expression.Constant(-1, typeof(int));
foreach (var propertyName in propertyNames)
{
foreach (var propertyValue in propertyValues)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
ConstantExpression valueExpression = Expression.Constant(propertyValue, typeof(string));
ConstantExpression caseInsensitiveComparisonExpression = Expression.Constant(StringComparison.CurrentCultureIgnoreCase, typeof(StringComparison));
MemberExpression propertyExpression = Expression.Property(entity, propertyInfo);
MethodCallExpression propertyLoweredExpression = Expression.Call(propertyExpression, toLowerInfo);
MethodCallExpression valueLoweredExpression = Expression.Call(valueExpression, toLowerInfo);
MethodCallExpression containsExpression = Expression.Call(propertyLoweredExpression, containsInfo, valueLoweredExpression);
masterExpression = Expression.Or(containsExpression, masterExpression);
}
}
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(masterExpression, entity);
return query.Where(lambda);
}
I am using it like so:
qry = QueryExpressions.ContainsQuery(qry, searchCols, new List<string> {universalSearch});
As I step through the code, qry starts out as an IQueryable select all statement. Then this method gets called and I check lambda and it is a nested list of or's as I expect. When I step out of the method though, qry remains a simple select all statement as before. My lambda expression is not being applied. No matter what I pass in as universalSearch, the qry returns the entire data set.
Thanks in advance.
Here is lambda after the method is finished executing:
{entity => (entity.STAKE_COMMENT.ToLower().Contains("other".ToLower()) Or (entity.NOTES.ToLower().Contains("other".ToLower()) Or (entity.COMMENT_SOURCE.ToLower().Contains("other".ToLower()) Or (entity.STAKE_COMMENT_DATE2.ToLower().Contains("other".ToLower()) Or (entity.COMMENT_STATUS.ToLower().Contains("other".ToLower()) Or (entity.COMMENT_CATEGORY.ToLower().Contains("other".ToLower()) Or (entity.COMMENTTOPIC.ToLower().Contains("other".ToLower()) Or (entity.STAKEHOLDER.ToLower().Contains("other".ToLower()) Or (True Or True)))))))))}
The problem is not that your query is unchanged but that your predicate is flawed.
You have this as predicate:
... lots of stuff ... Or (True Or True)
Every condition that contains or true will always evaluate to true.

How to use Expression(Of TDelegate).Update Method

I have built a repository using Lambda expressions to filter my entity collections. As a parameter to the method I'm sending Expression<Func<Case, bool>> exp. But inside the method I would like to update that same expression with some global filters. I can see that the expression object itself got an Update method, but I can't figure out how it is implemented (and cannot find anything while searching the net).
exp.Update(exp.Body, ???);
Could anyone give an example??
EDIT: Definition of the method: http://msdn.microsoft.com/en-us/library/ee378255.aspx
EDIT2: This is my code (where I try to use .And):
Expression<Func<Case, bool>> newExp = c => c.CaseStatusId != (int)CaseStatus.Finished
var binExp = Expression.And(exp.Body, newExp.Body);
ParameterExpression paramExp = Expression.Parameter(typeof(Expression<Func<Case, bool>>), "c");
return repository.Where(Expression.Lambda<Expression<Func<Case, bool>>>(binExp,
new[] { paramExp }).Compile()).ToArray();
It fails with the following ArgumentException: Lambda type parameter must be derived from System.Delegate
I don't think the Update method can help you here. It only creates a new lambda, but does not update the original parameters with the new one, you have to do it manually.
I'd recommend having a visitor that replaces the parameter, then you can And the expressions together.
In total you would get something like:
private Case[] getItems(Expression<Func<Case, bool>> exp)
{
return repository.Where(AddGlobalFilters(exp).Compile()).ToArray();
}
private Expression<Func<Case, bool>> AddGlobalFilters(Expression<Func<Case, bool>> exp)
{
// get the global filter
Expression<Func<Case, bool>> newExp = c => c.CaseStatusId != (int)CaseStatus.Finished;
// 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<Case, 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<Case, bool>>(binExp, newExp.Parameters);
}
/// <summary>
/// updates the parameter in the expression
/// </summary>
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);
}
}
System.Linq.Expressions.Expression.And(exp.Body, newExpression.Body);
exapmle:
Expression<Func<int, bool>> f = p => true;
var a = Expression.And(f.Body, f.Body);
ParameterExpression pParam = Expression.Parameter(typeof(int), "p");
var b = (new int[] { 1, 2, 3 }).Where(Expression.Lambda<Func<int, bool>>(a,
new ParameterExpression[] { pParam }).Compile());

Categories