объединить несколько лямбда-выражений с разными типами в одно выражение - PullRequest
2 голосов
/ 04 февраля 2020

Я хочу объединить несколько разделенных лямбда-выражений и построить их одно окончательное выражение.

примеры классов:

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; } 
}

каждый класс имеет одно лямбда-выражение:

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 

, поскольку ClassB имеет одно свойство ClassA, можно создать выражение с обоими условиями. пример:

// 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")        

если я хочу сгенерировать ожидаемое выражение во время выполнения, я должен каким-то образом преобразовать a параметр classARule в b.objectA

проблема в том, что я знаю, как объединить два выражения, но я не знаю, как заменить параметр a другим объектом. в этом случае b.objectA

Обновление - чтобы избежать путаницы

цель состоит в достижении Expression<Func<ClassB,bool>> expected выражения во время выполнения с использованием classARule и classBRule

Ответы [ 2 ]

1 голос
/ 05 февраля 2020

К счастью, я решил проблему. Окончательный результат здесь для других, если они сталкиваются с такой проблемой.

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;
    }
}

использование:

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

0 голосов
/ 05 февраля 2020

Вам нужно скомпилировать выражение:

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));
...