【问题标题】:combine multiple lambda expressions with different types to one expression将多个不同类型的 lambda 表达式组合成一个表达式
【发布时间】:2020-02-04 19:46:47
【问题描述】:

我想组合一些分离的 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")        

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

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


更新 - 避免更多混乱

目标是在运行时使用classARuleclassBRule实现Expression&lt;Func&lt;ClassB,bool&gt;&gt; expected表达式


【问题讨论】:

  • 您是否只想让您的 func 采用 2 个参数而不是 1 个参数?我有点困惑你在问什么
  • @Jonesopolis 不,我想根据类的每个属性动态组合所有规则。
  • 这些规则对我的数据库查询应用了一些限制,例如,因为 ObjectB 引用了 ObjectA,我想在运行时将 objectA 限制添加到最终查询中
  • 可以在编译时执行此操作并将a 更改为b.objectA 属性。像上面的例子。我的问题是我应该如何在运行时做到这一点?
  • @Jonesopolis 我写了很多表达式是过去但现在我也很困惑:) 我认为在这种情况下这是正常的。 xD

标签: c# expression lambda


【解决方案1】:

幸运的是,我解决了这个问题。 如果遇到这样的问题,这里的最终结果是给其他人的。

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

【讨论】:

  • 你还应该包括一个解释(代码中的一些cmets应该足够了)
  • @Nkosi 我在我的代码中添加了一些 cmets。希望它可以帮助您准确了解它的工作原理。
  • 我理解代码。我建议为那些将来来查看您的答案的人添加 cmets,他们可能不了解正在做什么以及为什么。
  • 很高兴您找到了解决方案。
【解决方案2】:

您需要编译表达式:

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

【讨论】:

  • 我的问题不是关于加载项目。我没有想要创建的 expected 表达式。
  • 我刚刚演示了表达式和编译。此代码有效(与编译一起),您的代码缺少编译,因此没有。缺少您要创建的表达式的哪一部分?
  • 是不是因为我使用了 ObjectA(此处未显示 - 在类定义中)和 ObjectB 而不是 ClassA 和 ClassB? - 无论如何都添加了它们。
  • 不,我只是重命名了类名以简化示例并避免更多混淆。 Expression&lt;Func&lt;ClassB, bool&gt;&gt; expected 这是我想使用其他两个表达式(A&B 规则)创建的示例。我不想手动创建这个表达式。 the goal is to achieve expected expression at runtime using classARule and classBRule
  • 啊,抱歉,误解了您要执行的操作。这将取决于您如何存储/提供每个类的表达式 - 您是否有一种方法可以检索它们以进行连接?他们是通过代表传递的吗?最后,您将需要构建循环遍历不同对象(ClassA、ClassB、ClassC 等)的表达式树以及要检查的属性以构建单个表达式。但是,单个表达式需要一个对象类型——您目前有两个。这让我觉得你必须一个接一个地检查每个表达式。
猜你喜欢
  • 1970-01-01
  • 2013-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-09
  • 1970-01-01
相关资源
最近更新 更多