Reputation: 5275
I want to combine some separated lambda expressions and build one final expression of them.
example classes :
class Address {
public string city { get; set; }
public string country { get; set; }
}
class ClassA {
public int Id { get; set; }
public Address address { get; set; }
}
class ClassB {
public int Id { get; set; }
public ClassA objectA { get; set; }
}
each class have one lambda expression :
Expression<Func<ClassA,bool>> classARule = a =>
a.Id > 1 && a.address.city == "city1" || a.address.country == "us"
Expression<Func<ClassB,bool>> classBRule = b => b.Id == 100
because ClassB
has one property of ClassA
it's possible to create an expression with both conditions. example :
// I want to create this expected object at runtime using classARule and classBRule
Expression<Func<ClassB,bool>> expected = b =>
(b.Id == 100) &&
(b.objectA.Id > 1 && b.objectA.address.city == "city1" || b.objectA.address.country == "us")
if I want to generate expected expression at runtime I should somehow convert a
parameter of classARule
to b.objectA
the problem is I know how to combine two expressions but I don't know how to replace a
parameter with some other object. in this case b.objectA
the goal is to achieve Expression<Func<ClassB,bool>> expected
expression at runtime using classARule
and classBRule
Upvotes: 3
Views: 507
Reputation: 5275
Fortunately, I solved the problem. The final result here is for others if they encounter such a problem.
public static Expression<Func<B, bool>> Combine<B, A>(this Expression<Func<B, bool>> expr1, Expression<Func<A, bool>> expr2, Expression<Func<B, A>> property)
{
// this is (q) parameter of my property
var replaceParameter = property.Parameters[0];
// replacing all (b) parameter with the (q)
// these two lines converts `b => b.Id == 100` to `q => q.Id == 100`
// using ReplaceExpVisitor class
var leftVisitor = new ReplaceExpVisitor(replaceParameter);
var left = leftVisitor.Visit(expr1.Body);
// the property body is 'q.objectA'
var replaceBody = property.Body;
// now i'm replacing every (a) parameter of my second expression to 'q.objectA'
// these two lines convert this statement:
// a.Id > 1 && a.address.city == "city1" || a.address.country == "us"
// to this :
// q.objectA.Id > 1 && q.objectA.address.city == "city1" || q.objectA.address.country == "us"
var rightVisitor = new ReplaceExpVisitor(replaceBody);
var right = rightVisitor.Visit(expr2.Body);
// creating new expression and pass (q) reference to it (replaceParameter).
return Expression.Lambda<Func<B, bool>>(Expression.AndAlso(left, right), replaceParameter);
}
// this is a simple class to replace all parameters with new expression
private class ReplaceExpVisitor : ExpressionVisitor
{
private readonly Expression _newval;
public ReplaceExpVisitor(Expression newval) => _newval = newval;
protected override Expression VisitParameter(ParameterExpression node)
{
return _newval;
}
}
usage :
var result = classBRule.Combine(classARule, q => q.objectA);
// or
Expression<Func<ClassB,bool>> result =
Combine<ClassB, ClassA>(classBRule, classARule, q => q.objectA);
/*
result is equal to the expected expression in the first example now
result output :
q =>
((q.Id == 100) &&
(((q.objectA.Id > 1) && (q.objectA.address.city == "city1")) ||
(q.objectA.address.country == "us")))
*/
https://dotnetfiddle.net/KnV3Dz
Upvotes: 2
Reputation: 1740
You'll need to compile the expression:
class Address
{
public string city { get; set; }
public string country { get; set; }
}
class ObjectA
{
public int Id { get; set; }
public Address address { get; set; }
}
class ObjectB
{
public int Id { get; set; }
public ObjectA objectA { get; set; }
}
Expression<Func<ObjectB, bool>> expected = b =>
(b.Id == 100) &&
(b.objectA.Id > 1 && b.objectA.address.city == "City1" || b.objectA.address.country == "US");
// Compile the Expression
var expectedItems = expected.Compile();
List<ObjectB> objBs = new List<ObjectB>();
var address = new Address();
var objA = new ObjectA();
var objB = new ObjectB();
address.city = "City1";
address.country = "US";
objA.Id = 1;
objB.Id = 100;
objA.address = address;
objB.objectA = objA;
objBs.Add(objB);
address = new Address();
objA = new ObjectA();
objB = new ObjectB();
address.city = "City2";
address.country = "US";
objA.Id = 3;
objB.Id = 100;
objA.address = address;
objB.objectA = objA;
objBs.Add(objB);
// Use expectedItems
var result = objBs.FirstOrDefault(b => expectedItems(b));
Upvotes: 0