将具有不同类型的多个Lambda表达式合并为一个表达式。

3

我想将一些分离的Lambda表达式组合起来,构建出一个最终表达式。

示例类:

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


每个类都有一个lambda表达式:
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")        

如果我想在运行时生成预期的表达式,我应该以某种方式将classARule的参数a转换为b.objectA

问题在于我知道如何组合两个表达式,但不知道如何用其他对象替换a参数。在这种情况下,是b.objectA


更新 - 为了避免更多的混淆

目标是使用classARuleclassBRule在运行时实现Expression<Func<ClassB,bool>> expected表达式



1
你是想让你的函数接受两个参数而不是一个吗?我有点困惑你在问什么。 - Jonesopolis
@Jonesopolis 不,我想根据类的每个属性动态组合所有规则。 - AliReza Sabouri
这些规则对我的数据库查询施加了一些限制,例如因为ObjectB引用了ObjectA,我想在运行时将ObjectA的限制添加到最终查询中。 - AliReza Sabouri
可以在编译时完成并将a更改为b.objectA属性,就像上面的例子一样。我的问题是如何在运行时完成这个操作? - AliReza Sabouri
@Jonesopolis 我以前写了很多表达式,但现在我也感到困惑 :) 我认为在这种情况下这是正常的。xD - AliReza Sabouri
显示剩余3条评论
2个回答

2

很幸运,我解决了这个问题。这里的最终结果是为其他人提供帮助,如果他们遇到类似的问题。

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


你还应该包含一些解释(代码中的一些注释就足够了)。 - Nkosi
@Nkosi 我在我的代码中添加了一些注释,希望它能帮助你准确地理解它的工作原理。 - AliReza Sabouri
我理解这段代码。我建议为那些未来来审查你的回答但可能不理解正在做什么以及为什么要这样做的人添加注释。 - Nkosi
很高兴你找到了解决方案。 - Nkosi

0

你需要编译这个表达式:

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

1
是因为我使用了ObjectA(未在此处显示-在类定义中)和ObjectB而不是ClassA和ClassB吗? - 无论如何都添加了它们。 - Steve Padmore
“Expression<Func<ClassB, bool>> expected”是我想通过两个其他表达式(A和B规则)创建的示例。我不想手动创建这个表达式。 目标是在运行时使用classARule和classBRule实现预期的表达式。 - AliReza Sabouri
1
啊,抱歉我误解了你想要做的事情。这将取决于您如何存储/提供每个类的表达式-您是否可以有一个可以检索它们以进行连接的方法?它们是通过委托传递的吗?最后,您需要构建表达式树,循环遍历不同的对象(ClassA、ClassB、ClassC等)以及要检查的属性来构建单个表达式。但是,单个表达式期望一个对象类型-您目前有两个。这让我想到您必须一个接一个地检查每个表达式。 - Steve Padmore
如果你有ClassA、ClassB和ClassC,那么表达式Func<Class?, bool>会期望什么?这让我想到你需要分别检查它们——而不是像你展示的那样使用单个表达式。我在这条路上对吗? - Steve Padmore
我的目标表达式类型是动态的。如果您看一下上面的示例,我想将两个表达式连接起来,因为ClassB引用了ClassA,所以我知道可以结合ClassA表达式和ClassB表达式。我正在尝试为这个问题提供我的解决方案,并将在接下来的几分钟内更新帖子。 - AliReza Sabouri
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接