如何使用动态对象作为 lambda 表达式的输入?

Posted

技术标签:

【中文标题】如何使用动态对象作为 lambda 表达式的输入?【英文标题】:How to work with dynamic objects as input for lambda expressions? 【发布时间】:2020-03-04 21:50:35 【问题描述】:

我想将表达式存储在数据库中以维护规则引擎。表达式就是这个例子的形式:

Expression<Func<MyTerm, bool>> expression = t => t.MyProperty == SomeValue;

我面临的问题是我必须在运行时创建 MyTerm。为了仍然能够使用 MyTerm 作为 lambda 表达式的参数,我使用 ExpandoObject 实现了一个解决方案,如下所示:

public class MyTerm

    ExpandoObject ParameterSet;

    public void AddParameter(string name, object value)
    
        IDictionary<string, object> dictionary = ParameterSet as IDictionary<string, object>;
        if (dictionary.ContainsKey(name))
        
            dictionary[name] = value;
        
        else
        
            dictionary.Add(name, value);
        
    

    public string GetString(string name)
    
        IDictionary<string, object> dictionary = ParameterSet as IDictionary<string, object>;
        if (dictionary.ContainsKey(name))
        
            return (string)dictionary[name];
        
        throw new KeyNotFoundException("Term does not contain parameter name.");
    

问题是,我获得了能够在运行时向 MyTerm 添加参数的优势,但是我必须为每个 getter 指定返回类型,而不是能够使用上面的语法:

t.MyProperty

有没有办法使用动态对象作为 lambda 表达式的输入,或者有没有办法为这样的类编写扩展方法以返回对象而不是特定类型?我可以像这样编译表达式:

public static Func<MyTerm, bool> Compile(string body)

    ParameterExpression term = Expression.Parameter(typeof(MyTerm), typeof(MyTerm).Name);
    LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[]  term , typeof(bool), body);
    return (Func<DynamicTerm, bool>)exp.Compile();


static void Main(string[] args)

    MyTerm term = new MyTerm();
    term.AddParameter("SomeParameter", "SomeValue");

    Func<MyTerm, bool> IsGreaterThanSomeParameter = Compile("MyTerm.GetString(\"SomeParameter\")== SomeValue);

    bool result = IsGreaterThanSomeParameter(term);

    Console.WriteLine(result);
    Console.ReadLine();

我想存储独立于特定实现的 lambda 表达式,因此如果另一个服务尝试将这些表达式编译回函数,则该语法应该是开箱即用的。没有任何工作方法的吸气剂。 提前谢谢你。

【问题讨论】:

我仍然不明白:1) 你如何在数据库中存储表达式,2) ParameterSet 与 MyTerm 的关系,3) 你为什么要尝试使用动态。如果你的表达式是固定的,它只会访问 MyTerm 上一组已知的属性,所以你不需要动态添加属性,确定吗? 1) 这与我面临的问题并没有真正的关系。我只是想为应用程序提供上下文。 2) ParameterSet 是一个 ExpandoObject,它使我能够在运行时向 MyTerm 类添加参数。 3)我的表情是固定的。但不是我的一组可能的表达方式。可以将参数添加到 MyTerm 中,也可以添加与这些参数相关的表达式 但是您不会在运行时将属性添加到 MyTerm。您添加了一个 GetString 方法,该方法恰好使用 ExpandoObject 作为字典,但您从不调用 GetString。你不会在任何地方使用dynamic 我添加了我的 AddParameter 方法。这是我在运行时添加参数的方法 您仍然没有调用GetString 的任何内容,并且您仍然没有将ExpandoObject 用作Dictionary&lt;string, object&gt; 以外的任何其他内容 【参考方案1】:

事实证明,如果您的对象上有一个索引器,System.Linq.Dynamic 会在找不到匹配的属性时尝试调用它。

public class DynamicTerm

    private readonly Dictionary<string, object> store = new Dictionary<string, object>();

    public void Set(string key, object value) => store[key] = value;
    public object this[string key] => store[key];


public static void Main()

    string body = "Term.SomeProperty == \"foo\"";
    ParameterExpression termExpr = Expression.Parameter(typeof(DynamicTerm), "Term");
    var exp = DynamicExpressionParser.ParseLambda(new[]  termExpr , typeof(bool), body);
    var compiled = (Func<DynamicTerm, bool>)exp.Compile();

    var term = new DynamicTerm();
    term.Set("SomeProperty", "foo");

    Console.WriteLine(compiled(term));

我找不到返回 typed 数据的方法(它将双方都转换为对象):但是它适用于字符串。也许这是一个起点。

【讨论】:

是的,这是一个很好的开始。但我认为为了让这个工作我需要这样的东西: public T this[string key]get /* Return generic type T. */ 确实,我找不到这样做的方法。你如何使用你的DynamicTerm——你是否只访问过它的属性并进行二进制比较?还是你做一些更复杂的事情?一种选择是按照我的回答做,然后使用ExpressionVisitorTerm.SomeProperty == (object)"foo" 变成(string)Term.SomeProperty == "foo" 实际上我只是使用 DynamicTerm 作为我的参数的包装器,以便进行一些简单的检查。问题是,我希望能够在连接表达式中使用多个参数。像 (t.Age >= 10 || t.Name == "Tom") 你的 DynamicTerm 在哪里t 啊是的。抱歉,我一直在混淆名称。

以上是关于如何使用动态对象作为 lambda 表达式的输入?的主要内容,如果未能解决你的问题,请参考以下文章

从对象集合动态构建 lambda 表达式?

Lambda如何使用?

SqlDataReader生成动态Lambda表达式

Lambda

从另外两个创建动态表达式 lambda(链接表达式)

如何使用 lambda 表达式作为模板参数?