lambda expression cast from interface - c#

I have following interface:
public interface IHasSchoolId
{
long? SchoolId { get; set; }
}
and following entity:
public class Student : IHasSchoolId
{
long? SchoolId { get; set; }
}
then there is following entity manager implementing empty interface IEntityManager<T>:
public class HasSchoolIdManager: IEntityManager<IHasSchoolId>
{
public static Expression<Func<IHasSchoolId, bool>> Filter()
{
return x=> x.SchoolId != null; //this is just an example
}
}
and finally I have following method:
private Expression<Func<TEntity, bool>> GetFilters<TEntity>()
{
Expression<Func<TEntity, bool>> result = null;
var entityManagers = //geting entitymanagers that are assinable to TEntity, like the one above and a lot more like IEntityManager<Student> itself
foreach (var entityManager in entityManagers)
{
var filterMethod = entityManager.GetMethod("Filter");
if (filterMethod != null)
result = AndAlso(result, (Expression<Func<TEntity, bool>>) filterMethod.Invoke(null, null)); //this line throws exception
}
return result;
}
and then when I'm calling the method like GetFilters<Student>() im getting following exception:
System.InvalidCastException: 'Unable to cast object of type 'System.Linq.Expressions.Expression[System.Func[WebCore.Models.Infrastructure.Interfaces.IHasSchoolId,System.Boolean]]' to type 'System.Linq.Expressions.Expression[System.Func[WebCore.Models.Student,System.Boolean]]'.'
by the way, this is my AndAlso method which works fine:
private Expression<Func<T, bool>> AndAlso<T>(
Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
if (expr1 == null)
return expr2;
if (expr2 == null)
return expr1;
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
and here is my empty IEntityManager interface:
public interface IEntityManager<T>
{
}
Note: not all my entities implements IHasSchoolId interface, there are many other interfaces, this is just an example
i think my question is, how to cast from Expression<Func</*an interface*/, bool>> to Expression<Func</*a class that implements that interface*/, bool>>
using reflection because as you can see, im calling the filter method using reflection

here is what i finally did:
Expression<Func<T, bool>> expression = null;
var managers = GetEntityManagers<T>();
foreach (var manager in managers)
{
var expr2 = manager.GetMethod("Filter")?.Invoke(null, null) as dynamic;
if (expr2 == null) continue;
var transformedExpr = Transform<T>(expr2) as Expression<Func<T, bool>>;
expression = expression == null ? transformedExpr : expression.AndAlso(transformedExpr);
}
and here is the transform function:
private class Visitor : ExpressionVisitor
{
private Expression _parameter;
public Visitor(Expression parameter)
{
_parameter = parameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameter;
}
}
private static Expression<Func<T, bool>> Transform<T>(dynamic expression)
{
ParameterExpression parameter = Expression.Parameter(typeof(T));
Expression body = new Visitor(parameter).Visit(expression.Body);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}

Related

FilterByItems Method not working correctly

Im building a filter and im using the FilterByItems method to compare two Arrays(one from my frontend and one from my database). It all goes through a Overlay Panel in my Frontend, where the user can select different persontypes like member, worker and much more. So my problem is now that i got multiple persontype hovers which dont want do work together. If i just have one it works fine, if i add a second one it only works if both have the same values in the frontend list.
The right case would be:
Frontend Panel one got the Member in the Array, Frontend Panel two got the Friend in the Array.
Table shows all persons who are saved with these types in their profile.
The current and wrong case is:
Panel 1 and Panel two are not working with different arrays and only showing informations if both have the same list for example both got member on position [0]
Generelly it seems like it works as a single one as an and query and as the second panel joins in it blocks completely. So here is my code(FilterByItems Method is linked above):
//The Filter
//Login
[HttpPost("filter/")]
public async Task<IActionResult> Filter([FromBody] Filter user)
{
var baseQuery = _context.Personens.AsQueryable();
//Personentyp 1 Dont works in combination with below
if (user.personenTypFilter.Length > 0)
baseQuery = baseQuery.FilterByItems(user.personenTypFilter, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)), true);
////Personentyp 2
//if (user.personenTypFilter2.Length > 0)
// baseQuery = baseQuery.FilterByItems(user.personenTypFilter2, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)), true);
//---------------------------------
var result = await (baseQuery.Select(p => new
{
personId = p.PersonId,
nachname = p.Nachname,
vorname = p.Vorname,
plz = p.Plz,
firmBez = p.Firmenbezeichnung,
ort = p.Ort,
personentyp = p.Personentypzuordnungens.Select(i => new
{
personentypId = i.PersonentypId,
}),
aktuellePosition = p.AktuellePosition,
taetigkeit = p.Tätigkeit,
kernkompetenzen = p.Kernkompetenzen,
datenReviewedZeitpunkt = p.DatenReviewedZeitpunkt,
}).ToListAsync());
return Ok(result);
}
Thats how i declared my variables in my Filter Model:
public string[] personenTypFilter { get; set; }
public string[] personenTypFilter2 { get; set; }
New problem with .CombineAnd(from comments)
baseQuery = baseQuery.Where(
//Characteristics
character1Predicate.CombineOr(character1Predicate2).CombineOr(character1Predicate3)
//Persontypes
.CombineAnd(personType1Predicate.CombineOr(personType2Predicate).CombineOr(personType3Predicate)));
This function is evolution of FilterByItems and adds additional public methods for generating predicate and combining them.
You can use new extensions in your query in the following way:
var baseQuery = _context.Personens.AsQueryable();
var predicate1 = baseQuery.GetItemsPredicate(ser.personenTypFilter, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)));
var predicate2 = baseQuery.GetItemsPredicate(user.personenTypFilter2, (m, k) => m.Personentypzuordnungens.Any(i => i.Personentyp.Bezeichnung.Contains(k)));
// filter by combined predicates
baseQuery = baseQuery.Where(predicate1.CombineOr(predicate2));
And implementation:
public static class QueryableExtensions
{
public static IQueryable<T> FilterByItems<T, TItem>(this IQueryable<T> query, IEnumerable<TItem> items,
Expression<Func<T, TItem, bool>> filterPattern, bool isOr = true, bool emptyValue = true)
{
var filterLambda = query.GetItemsPredicate(items, filterPattern, isOr, emptyValue);
return query.Where(filterLambda);
}
public static Expression<Func<T, bool>> GetItemsPredicate<T, TItem>(this IEnumerable<T> query, IEnumerable<TItem> items, Expression<Func<T, TItem, bool>> filterPattern, bool isOr = true, bool emptyValue = false)
{
Expression predicate = null;
foreach (var item in items)
{
var itemExpr = Expression.Constant(item);
var itemCondition = ExpressionReplacer.Replace(filterPattern.Body, filterPattern.Parameters[1], itemExpr);
if (predicate == null)
predicate = itemCondition;
else
{
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
itemCondition);
}
}
predicate ??= Expression.Constant(emptyValue);
var filterLambda = Expression.Lambda<Func<T, bool>>(predicate, filterPattern.Parameters[0]);
return filterLambda;
}
public static Expression<Func<T, bool>> CombineOr<T>(this Expression<Func<T, bool>> predicate1,
Expression<Func<T, bool>> predicate2)
{
var parameter = predicate1.Parameters[0];
var body = Expression.OrElse(predicate1.Body, ExpressionReplacer.GetBody(predicate2, parameter));
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
public static Expression<Func<T, bool>> CombineAnd<T>(this Expression<Func<T, bool>> predicate1,
Expression<Func<T, bool>> predicate2)
{
var parameter = predicate1.Parameters[0];
var body = Expression.AndAlso(predicate1.Body, ExpressionReplacer.GetBody(predicate2, parameter));
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
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 node)
{
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
return replacement;
return base.Visit(node);
}
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.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}

Expression parameter is not defined

I am trying to perform a query against a list to give immediate results using an expression that is set elsewhere in the code, while a second thread goes off and uses it to get a full set of results from a database in a Linq query.
I know that the expression itself is OK as when I send it over the wire to the server side and apply it against an IQueryable then it will work. However, when applied on the client side the following error is produced:
System.InvalidOperationException: 'variable 'item' of type 'MissionControlSuite.Shared.Interface.Model.IBox' referenced from scope '', but it is not defined'
This code that performs the filtering:
public abstract class GridWithPaging<T> where T : class
{
List<T> _items
public Expression<Func<T, bool>> Filter { get; set; }
public ObservableCollection<IGridFilter<T>> Filters { get; set; }
// more code skipped for breveity
public void FiltersChanged(object sender, EventArgs e)
{
Expression<Func<T, bool>> combined = item => true;
foreach (var filter in Filters)
combined = Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(filter.Expression.Body, combined.Body),
combined.Parameters.Single());
Filter = combined;
NotifyPropertyChanged("Filter");
}
public void AddFilter(IGridFilter<T> filter)
{
Filters.Add(filter);
NotifyPropertyChanged("Filters");
}
private void DoFiltering(int start)
{
var newView = _items.Skip(start);
if(Filter != null)
newView = newView.AsQueryable().Where(Filter);
//some more code that acts on the filtered list
}
}
The expression is set elsewhere like this:
Expression<Func<IBox, bool>> expression = item => item.BoxId.Contains(v);
var filter = new GridFilter<IBox>()
{
Name = string.Format("{0} contains {1}", Property, Value),
Expression = expression
};
Grid.AddFilter(filter);
This answer will help you: Combining two expressions (Expression<Func<T, bool>>)
To summarize: The problem is that the parameters, although with the same name, are not the same instance of the ParameterExpression - which is included in the comment to that answer.
The solution is to use a Visitor (code copied over from linked post):
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
Also note: the ORM provider (or another IQueryable you use) probably works because it translates that to text directly, whereas executing (Invokeing) this LambdaExpression causes the error as it's more strict, in a way. Some IQueryables can also accept simple string parameters and parse that on their own, which should indicate that they have a wider range of acceptable inputs.

Concatenating lambda expressions [duplicate]

I have two expressions of type Expression<Func<T, bool>> and I want to take to OR, AND or NOT of these and get a new expression of the same type
Expression<Func<T, bool>> expr1;
Expression<Func<T, bool>> expr2;
...
//how to do this (the code below will obviously not work)
Expression<Func<T, bool>> andExpression = expr AND expr2
Well, you can use Expression.AndAlso / OrElse etc to combine logical expressions, but the problem is the parameters; are you working with the same ParameterExpression in expr1 and expr2? If so, it is easier:
var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
This also works well to negate a single operation:
static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr)
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expr.Body), expr.Parameters[0]);
}
Otherwise, depending on the LINQ provider, you might be able to combine them with Invoke:
// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}
Somewhere, I have got some code that re-writes an expression-tree replacing nodes to remove the need for Invoke, but it is quite lengthy (and I can't remember where I left it...)
Generalized version that picks the simplest route:
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
Starting from .NET 4.0, there is the ExpressionVisitor class which allows you to build expressions that are EF safe.
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
You can use Expression.AndAlso / OrElse to combine logical expressions, but you have to make sure the ParameterExpressions are the same.
I was having trouble with EF and the PredicateBuilder so I made my own without resorting to Invoke, that I could use like this:
var filterC = filterA.And(filterb);
Source code for my PredicateBuilder:
public static class PredicateBuilder {
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
And the utility class to substitute the parameters in a lambda:
internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node) {
Expression newValue;
if (subst.TryGetValue(node, out newValue)) {
return newValue;
}
return node;
}
}
If you provider does not support Invoke and you need to combine two expression, you can use an ExpressionVisitor to replace the parameter in the second expression by the parameter in the first expression.
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);
}
}
static Expression<Func<T, bool>> UpdateParameter<T>(
Expression<Func<T, bool>> expr,
ParameterExpression newParameter)
{
var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
var body = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
[TestMethod]
public void ExpressionText()
{
string text = "test";
Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);
var expr4 = Expression.Lambda<Func<Recording, bool>>(
Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);
var func = expr4.Compile();
Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
}
Nothing new here but married this answer with this answer and slightly refactored it so that even I understand what's going on:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
}
I combined some beautiful answers here to make it possible to easily support more Expression operators.
This is based on the answer of #Dejan but now it's quite easy to add the OR as well. I chose not to make the Combine function public, but you could do that to be even more flexible.
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression) =>
Combine(leftExpression, rightExpression, Expression.AndAlso);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression) =>
Combine(leftExpression, rightExpression, Expression.Or);
public static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> leftExpression, Expression<Func<T, bool>> rightExpression, Func<Expression, Expression, BinaryExpression> combineOperator)
{
var leftParameter = leftExpression.Parameters[0];
var rightParameter = rightExpression.Parameters[0];
var visitor = new ReplaceParameterVisitor(rightParameter, leftParameter);
var leftBody = leftExpression.Body;
var rightBody = visitor.Visit(rightExpression.Body);
return Expression.Lambda<Func<T, bool>>(combineOperator(leftBody, rightBody), leftParameter);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return ReferenceEquals(node, _oldParameter) ? _newParameter : base.VisitParameter(node);
}
}
}
Usage is not changed and still like this:
Expression<Func<Result, bool>> noFilterExpression = item => filters == null;
Expression<Func<Result, bool>> laptopFilterExpression = item => item.x == ...
Expression<Func<Result, bool>> dateFilterExpression = item => item.y == ...
var combinedFilterExpression = noFilterExpression.Or(laptopFilterExpression.AndAlso(dateFilterExpression));
efQuery.Where(combinedFilterExpression);
(This is an example based on my actual code, but read it as pseudo-code)
I needed to achieve the same results, but using something more generic (as the type was not known). Thanks to marc's answer I finally figured out what I was trying to achieve:
public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp)
{
var parameter = Expression.Parameter(sourceType);
var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
var left = leftVisitor.Visit(exp.Body);
var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
var right = rightVisitor.Visit(newExp.Body);
var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
}
I suggest one more improvement to PredicateBuilder and ExpressionVisitor solutions. I called it UnifyParametersByName and you can find it in MIT library of mine: LinqExprHelper. It allows for combining arbitary lambda expressions. Usually the questions are asked about predicate expression, but this idea extends to projection expressions as well.
The following code employs a method ExprAdres which creates a complicated parametrized expression, using inline lambda. This complicated expression is coded only once, and then reused, thanks to the LinqExprHelper mini-library.
public IQueryable<UbezpExt> UbezpFull
{
get
{
System.Linq.Expressions.Expression<
Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> expr =
(u, parAdrM, parAdrZ) => new UbezpExt
{
Ub = u,
AdrM = parAdrM,
AdrZ = parAdrZ,
};
// From here an expression builder ExprAdres is called.
var expr2 = expr
.ReplacePar("parAdrM", ExprAdres("M").Body)
.ReplacePar("parAdrZ", ExprAdres("Z").Body);
return UBEZPIECZONY.Select((Expression<Func<UBEZPIECZONY, UbezpExt>>)expr2);
}
}
And this is the subexpression building code:
public static Expression<Func<UBEZPIECZONY, UBEZP_ADRES>> ExprAdres(string sTyp)
{
return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp)
.OrderByDescending(a => a.DATAOD).FirstOrDefault();
}
What I tried to achieve was to perform parametrized queries without need to copy-paste and with ability to use inline lambdas, which are so pretty. Without all these helper-expression stuff, I would be forced to create whole query in one go.
using System;
using System.Linq.Expressions;
namespace Extensions
{
public class Example
{
//How to use it
public static void Main()
{
Expression<Func<string, bool>> expression1 = exp => true;
Expression<Func<string, bool>> expression2 = exp => false;
Expression<Func<string, bool>> expression3 = ExpressionExtensions.AndAlso(expression1, expression2);
Expression<Func<string, bool>> expression4 = ExpressionExtensions.OrElse(expression1, expression2);
Expression<Func<string, bool>> expression = ExpressionExtensions.AndAlso(expression3, expression4);
}
}
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
}
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, body2WithParam1), parameter1);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
}
}
One more solution (with no ExpressionVisitor) via one method and small enum to identity expression operation type
private static Expression<Func<T, bool>> BindExpressions<T>(ExpressionOperationType operationType, Expression<Func<T, bool>>[] expressionPredicates)
{
var filterExpressionPredicate = expressionPredicates.FirstOrDefault() ?? (x => false);
if (expressionPredicates.Length > 1)
for (int i = 1; i < expressionPredicates.Length; i++)
{
var expressionBody = Expression.Invoke(expressionPredicates[i], filterExpressionPredicate?.Parameters);
var handledExpressionUnits = operationType switch
{
ExpressionOperationType.AndAlso => Expression.AndAlso(filterExpressionPredicate.Body, expressionBody),
_ => Expression.OrElse(filterExpressionPredicate.Body, expressionBody),
};
filterExpressionPredicate = Expression.Lambda<Func<T, bool>>(handledExpressionUnits, filterExpressionPredicate.Parameters);
}
return filterExpressionPredicate;
}
enum ExpressionOperationType
{
AndAlso = 0,
OrElse = 1
}
For instance:
We have a model AuditLog
public class AuditLog
{
public Guid Id { get; set; }
public string OldValues { get; set; }
public string NewValues { get; set; }
public DateTime Timestamp { get; set; }
}
And we want to build specific query: search all audit records with key words "oranges", "cars", "birds" within the boundaries of date (Timestamp)
public IQueryable<AuditLog> BuildQuery()
{
var query = _context.AuditLogs.AsNoTracking();
var commonFilterList = new List<Expression<Func<AuditLog, bool>>>();
commonFilterList.Add(x => x.Timestamp >= DateTime.Now);
commonFilterList.Add(x => x.Timestamp <= DateTime.Now.AddDays(1));
//real world such simple filter case I would use way like:
//query = query
// .Where(x => x.Timestamp >= DateTime.Now)
// .Where(x => x.Timestamp <= DateTime.Now.AddDays(1));
//but this point we keep the example
//using AndAlso
query = query.Where(BindExpressions(ExpressionOperationType.AndAlso, commonFilterList.ToArray()));
//at this point we look at more useful example of using BindExpressions implementation via OrElse expression operation type
var specificFilterList = new List<Expression<Func<AuditLog, bool>>>();
var keyWordsToSearch = new List<string>() { "oranges", "cars", "birds" };
foreach (var keyWord in keyWordsToSearch)
{
//real world we would to use EF.Functions.Contains / EF.Functions.FreeText statements <- but it is question another scope
specificFilterList.Add(x => EF.Functions.Like(x.NewValues, $"%{keyWord}%"));
specificFilterList.Add(x => EF.Functions.Like(x.OldValues, $"%{keyWord}%"));
}
//using OrElse
query = query.Where(BindExpressions(ExpressionOperationType.OrElse, specificFilterList.ToArray()));
//as result we get commonFilterList AND specificFilterList
return query;
}
I think this works fine, isn't it ?
Func<T, bool> expr1 = (x => x.Att1 == "a");
Func<T, bool> expr2 = (x => x.Att2 == "b");
Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
Func<T, bool> NOTexpr1 = (x => !expr1(x));

Combining multiple expressions trees

I'm getting the following error
The parameter 'p' was not bound in the specified LINQ to Entities
query expression.
I understand the problem (same instance of ParameterExpression should be used with all the expressions in the tree) and have attempted to use solutions I've found online but with no luck.
This is my method
private void SeedEntity<TEntity>(DatabaseContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions) where TEntity : class
{
Expression<Func<TEntity, bool>> allExpresions = null;
var parameters = identifierExpressions.SelectMany(x => x.Parameters).GroupBy(x => x.Name).Select(p => p.First()).ToList();
foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions)
{
Func<TEntity, object> vv = identifierExpression.Compile();
object constant = vv(entity);
ConstantExpression constExp = Expression.Constant(constant, typeof(object));
BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp);
Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, parameters);
if (allExpresions == null)
{
allExpresions = equalExpression2;
}
else
{
BinaryExpression bin = Expression.And(allExpresions.Body, equalExpression2.Body);
allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, parameters);
}
}
TEntity existingEntity = null;
if (allExpresions != null)
{
existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions);
}
if (existingEntity == null)
{
context.Set<TEntity>().Add(entity);
}
else
{
entity = existingEntity;
}
}
It generates an expression for the lookup of an entity based on a number of properties.
It works fine for a single expression, the error only occurs when passing in multiple.
Called like this:
SeedEntity(context, ref e, p=> p.Name);//Works
SeedEntity(context, ref e, p=> p.Name, p=> p.Age);//Fails
It generates something similar to me performing the following:
context.Set<TEntity>().FirstOrDefault(p=>p.Name == e.Name && p.Age == e.Age);
Replacing e.Name && e.Age with a ConstantExpression
You can see in the method above I grab all of the unique params and store them in parameters at the top, then use the same variable throughout.This is the start, but then I need to replace the instances of the parameter in each of the Expression<Func<TEntity, bool>> passed in as the params array, this is where I'm failing.
I've tried enumerate the expressions and use the .Update() method passing in the params
I also tried a solution using the ExpressionVisitor
public class ExpressionSubstitute : ExpressionVisitor
{
public readonly Expression from, to;
public ExpressionSubstitute(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
if (node == from) return to;
return base.Visit(node);
}
}
public static class ExpressionSubstituteExtentions
{
public static Expression<Func<TEntity, TReturnType>> RewireLambdaExpression<TEntity, TReturnType>(Expression<Func<TEntity, TReturnType>> expression, ParameterExpression newLambdaParameter)
{
var newExp = new ExpressionSubstitute(expression.Parameters.Single(), newLambdaParameter).Visit(expression);
return (Expression<Func<TEntity, TReturnType>>)newExp;
}
}
You're really close. I don't see the point of your parameters variable. Grouping them by name is a mistake. Why not just pass the parameters from the expression? Then visit if necessary. Your visitor code is fine.
private static void SeedEntity<TEntity>(DbContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions)
where TEntity : class
{
Expression<Func<TEntity, bool>> allExpresions = null;
foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions)
{
Func<TEntity, object> vv = identifierExpression.Compile();
object constant = vv(entity);
ConstantExpression constExp = Expression.Constant(constant, typeof(object));
BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp);
Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, identifierExpression.Parameters);
if (allExpresions == null)
{
allExpresions = equalExpression2;
}
else
{
var visitor = new ExpressionSubstitute(allExpresions.Parameters[0], identifierExpression.Parameters[0]);
var modifiedAll = (Expression<Func<TEntity,bool>>)visitor.Visit(allExpresions);
BinaryExpression bin = Expression.And(modifiedAll.Body, equalExpression2.Body);
allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, identifierExpression.Parameters);
}
}
TEntity existingEntity = null;
if (allExpresions != null)
{
existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions);
}
if (existingEntity == null)
{
context.Set<TEntity>().Add(entity);
}
else
{
entity = existingEntity;
}
}

Combining two expressions (Expression<Func<T, bool>>)

I have two expressions of type Expression<Func<T, bool>> and I want to take to OR, AND or NOT of these and get a new expression of the same type
Expression<Func<T, bool>> expr1;
Expression<Func<T, bool>> expr2;
...
//how to do this (the code below will obviously not work)
Expression<Func<T, bool>> andExpression = expr AND expr2
Well, you can use Expression.AndAlso / OrElse etc to combine logical expressions, but the problem is the parameters; are you working with the same ParameterExpression in expr1 and expr2? If so, it is easier:
var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
This also works well to negate a single operation:
static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr)
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expr.Body), expr.Parameters[0]);
}
Otherwise, depending on the LINQ provider, you might be able to combine them with Invoke:
// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}
Somewhere, I have got some code that re-writes an expression-tree replacing nodes to remove the need for Invoke, but it is quite lengthy (and I can't remember where I left it...)
Generalized version that picks the simplest route:
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
Starting from .NET 4.0, there is the ExpressionVisitor class which allows you to build expressions that are EF safe.
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
You can use Expression.AndAlso / OrElse to combine logical expressions, but you have to make sure the ParameterExpressions are the same.
I was having trouble with EF and the PredicateBuilder so I made my own without resorting to Invoke, that I could use like this:
var filterC = filterA.And(filterb);
Source code for my PredicateBuilder:
public static class PredicateBuilder {
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
And the utility class to substitute the parameters in a lambda:
internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node) {
Expression newValue;
if (subst.TryGetValue(node, out newValue)) {
return newValue;
}
return node;
}
}
If you provider does not support Invoke and you need to combine two expression, you can use an ExpressionVisitor to replace the parameter in the second expression by the parameter in the first expression.
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);
}
}
static Expression<Func<T, bool>> UpdateParameter<T>(
Expression<Func<T, bool>> expr,
ParameterExpression newParameter)
{
var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
var body = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
[TestMethod]
public void ExpressionText()
{
string text = "test";
Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);
var expr4 = Expression.Lambda<Func<Recording, bool>>(
Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);
var func = expr4.Compile();
Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
}
Nothing new here but married this answer with this answer and slightly refactored it so that even I understand what's going on:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
}
I combined some beautiful answers here to make it possible to easily support more Expression operators.
This is based on the answer of #Dejan but now it's quite easy to add the OR as well. I chose not to make the Combine function public, but you could do that to be even more flexible.
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression) =>
Combine(leftExpression, rightExpression, Expression.AndAlso);
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression) =>
Combine(leftExpression, rightExpression, Expression.Or);
public static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> leftExpression, Expression<Func<T, bool>> rightExpression, Func<Expression, Expression, BinaryExpression> combineOperator)
{
var leftParameter = leftExpression.Parameters[0];
var rightParameter = rightExpression.Parameters[0];
var visitor = new ReplaceParameterVisitor(rightParameter, leftParameter);
var leftBody = leftExpression.Body;
var rightBody = visitor.Visit(rightExpression.Body);
return Expression.Lambda<Func<T, bool>>(combineOperator(leftBody, rightBody), leftParameter);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return ReferenceEquals(node, _oldParameter) ? _newParameter : base.VisitParameter(node);
}
}
}
Usage is not changed and still like this:
Expression<Func<Result, bool>> noFilterExpression = item => filters == null;
Expression<Func<Result, bool>> laptopFilterExpression = item => item.x == ...
Expression<Func<Result, bool>> dateFilterExpression = item => item.y == ...
var combinedFilterExpression = noFilterExpression.Or(laptopFilterExpression.AndAlso(dateFilterExpression));
efQuery.Where(combinedFilterExpression);
(This is an example based on my actual code, but read it as pseudo-code)
I needed to achieve the same results, but using something more generic (as the type was not known). Thanks to marc's answer I finally figured out what I was trying to achieve:
public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp)
{
var parameter = Expression.Parameter(sourceType);
var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
var left = leftVisitor.Visit(exp.Body);
var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
var right = rightVisitor.Visit(newExp.Body);
var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
}
I suggest one more improvement to PredicateBuilder and ExpressionVisitor solutions. I called it UnifyParametersByName and you can find it in MIT library of mine: LinqExprHelper. It allows for combining arbitary lambda expressions. Usually the questions are asked about predicate expression, but this idea extends to projection expressions as well.
The following code employs a method ExprAdres which creates a complicated parametrized expression, using inline lambda. This complicated expression is coded only once, and then reused, thanks to the LinqExprHelper mini-library.
public IQueryable<UbezpExt> UbezpFull
{
get
{
System.Linq.Expressions.Expression<
Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> expr =
(u, parAdrM, parAdrZ) => new UbezpExt
{
Ub = u,
AdrM = parAdrM,
AdrZ = parAdrZ,
};
// From here an expression builder ExprAdres is called.
var expr2 = expr
.ReplacePar("parAdrM", ExprAdres("M").Body)
.ReplacePar("parAdrZ", ExprAdres("Z").Body);
return UBEZPIECZONY.Select((Expression<Func<UBEZPIECZONY, UbezpExt>>)expr2);
}
}
And this is the subexpression building code:
public static Expression<Func<UBEZPIECZONY, UBEZP_ADRES>> ExprAdres(string sTyp)
{
return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp)
.OrderByDescending(a => a.DATAOD).FirstOrDefault();
}
What I tried to achieve was to perform parametrized queries without need to copy-paste and with ability to use inline lambdas, which are so pretty. Without all these helper-expression stuff, I would be forced to create whole query in one go.
using System;
using System.Linq.Expressions;
namespace Extensions
{
public class Example
{
//How to use it
public static void Main()
{
Expression<Func<string, bool>> expression1 = exp => true;
Expression<Func<string, bool>> expression2 = exp => false;
Expression<Func<string, bool>> expression3 = ExpressionExtensions.AndAlso(expression1, expression2);
Expression<Func<string, bool>> expression4 = ExpressionExtensions.OrElse(expression1, expression2);
Expression<Func<string, bool>> expression = ExpressionExtensions.AndAlso(expression3, expression4);
}
}
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
}
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
ParameterExpression parameter1 = expr1.Parameters[0];
var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
var body2WithParam1 = visitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, body2WithParam1), parameter1);
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
}
}
One more solution (with no ExpressionVisitor) via one method and small enum to identity expression operation type
private static Expression<Func<T, bool>> BindExpressions<T>(ExpressionOperationType operationType, Expression<Func<T, bool>>[] expressionPredicates)
{
var filterExpressionPredicate = expressionPredicates.FirstOrDefault() ?? (x => false);
if (expressionPredicates.Length > 1)
for (int i = 1; i < expressionPredicates.Length; i++)
{
var expressionBody = Expression.Invoke(expressionPredicates[i], filterExpressionPredicate?.Parameters);
var handledExpressionUnits = operationType switch
{
ExpressionOperationType.AndAlso => Expression.AndAlso(filterExpressionPredicate.Body, expressionBody),
_ => Expression.OrElse(filterExpressionPredicate.Body, expressionBody),
};
filterExpressionPredicate = Expression.Lambda<Func<T, bool>>(handledExpressionUnits, filterExpressionPredicate.Parameters);
}
return filterExpressionPredicate;
}
enum ExpressionOperationType
{
AndAlso = 0,
OrElse = 1
}
For instance:
We have a model AuditLog
public class AuditLog
{
public Guid Id { get; set; }
public string OldValues { get; set; }
public string NewValues { get; set; }
public DateTime Timestamp { get; set; }
}
And we want to build specific query: search all audit records with key words "oranges", "cars", "birds" within the boundaries of date (Timestamp)
public IQueryable<AuditLog> BuildQuery()
{
var query = _context.AuditLogs.AsNoTracking();
var commonFilterList = new List<Expression<Func<AuditLog, bool>>>();
commonFilterList.Add(x => x.Timestamp >= DateTime.Now);
commonFilterList.Add(x => x.Timestamp <= DateTime.Now.AddDays(1));
//real world such simple filter case I would use way like:
//query = query
// .Where(x => x.Timestamp >= DateTime.Now)
// .Where(x => x.Timestamp <= DateTime.Now.AddDays(1));
//but this point we keep the example
//using AndAlso
query = query.Where(BindExpressions(ExpressionOperationType.AndAlso, commonFilterList.ToArray()));
//at this point we look at more useful example of using BindExpressions implementation via OrElse expression operation type
var specificFilterList = new List<Expression<Func<AuditLog, bool>>>();
var keyWordsToSearch = new List<string>() { "oranges", "cars", "birds" };
foreach (var keyWord in keyWordsToSearch)
{
//real world we would to use EF.Functions.Contains / EF.Functions.FreeText statements <- but it is question another scope
specificFilterList.Add(x => EF.Functions.Like(x.NewValues, $"%{keyWord}%"));
specificFilterList.Add(x => EF.Functions.Like(x.OldValues, $"%{keyWord}%"));
}
//using OrElse
query = query.Where(BindExpressions(ExpressionOperationType.OrElse, specificFilterList.ToArray()));
//as result we get commonFilterList AND specificFilterList
return query;
}
I think this works fine, isn't it ?
Func<T, bool> expr1 = (x => x.Att1 == "a");
Func<T, bool> expr2 = (x => x.Att2 == "b");
Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
Func<T, bool> NOTexpr1 = (x => !expr1(x));

Categories