【问题标题】:How to work with dynamic objects as input for lambda expressions?如何使用动态对象作为 lambda 表达式的输入?
【发布时间】: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; 以外的任何其他内容

标签: c# asp.net dynamic lambda expandoobject


【解决方案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——你是否只访问过它的属性并进行二进制比较?还是你做一些更复杂的事情?一种选择是按照我的回答做,然后使用ExpressionVisitor 将Term.SomeProperty == (object)"foo" 变成(string)Term.SomeProperty == "foo"
  • 实际上我只是使用 DynamicTerm 作为我的参数的包装器,以便进行一些简单的检查。问题是,我希望能够在连接表达式中使用多个参数。像 (t.Age >= 10 || t.Name == "Tom")
  • 你的 DynamicTerm 在哪里t?
  • 啊是的。抱歉,我一直在混淆名称。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-07
  • 1970-01-01
  • 2013-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多