【问题标题】:Dynamic lambda parameters based on dictionary elements基于字典元素的动态 lambda 参数
【发布时间】:2021-11-08 21:18:17
【问题描述】:

我的计划是创建一个查询,但参数基于字典。 Dictionary 包含字符串键和布尔值。 可以是字典中的 2 个或 3 个或更多项。

Dictionary<string, bool> items = new Dictionary<string, bool>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", true);

这是我要发送的字典,基于此我想动态创建一个查询,例如

.Where(e => e.CostFree == true || Visible == true || Closed == true)

但字典可以包含 2、3 或 4 个项目。

我该如何解决这个问题? 提前致谢

【问题讨论】:

  • 是否需要将查询传递给 EF?
  • 也许你需要使用字典键和值; item["CostFree"] 的值为 true 或者您可以使用 enum 作为您的密钥
  • 是的,我必须将它传递给 EF。基本上我猜布尔值总是正确的,因为我正在发送已调整的参数,所以我什至不确定我是否需要字典。但是没有字典仍然不知道如何动态创建查询...
  • 或者遍历你的字典并计算true值的数量。
  • 我的计划是创建一个查询,但参数基于字典 - 为什么?

标签: c# lambda


【解决方案1】:

LINQ 表达式可以通过System.Linq.Expressions.Expression 类上公开的静态方法轻松构建。 这是一个满足您需求的示例,假设您要针对命名为 SomeClass 的实体构建表达式

[TestMethod]
public void MyTestMethod()
{
    var testData = new List<SomeClass>()
    {
        new SomeClass() {Id=1, CostFree = false, Closed='N', Visible=false},
        new SomeClass() {Id=2, CostFree = true, Closed='N', Visible=false},     // expect only this one  matching
    };

    var items = new Dictionary<string, object>();
    items.Add("CostFree", true);
    items.Add("Visible", true);
    items.Add("Closed", 'Y');

    // this one will be the "e" in "e => e.CostFree == true || Visible == true || Closed == 'Y'"
    var paramExpression = Expression.Parameter(typeof(SomeClass));

    // lets construct the body ("e.CostFree == true || Visible == true || Closed == 'Y'") part step-by-step
    // the parts consists of binary "equals" expressions combined via logical "or" expression
    var bodyExpression = (Expression)null;
    foreach(var kvp in items)
    {
        // get the named property ("CostFree", ...) reference of paramExpression. this is the left hand side of "equals"
        var propertyExpression = Expression.PropertyOrField(paramExpression, kvp.Key);
        // get the constant with appropriate value to place on right hand side of "equals"
        var constantExpression = Expression.Constant(kvp.Value, kvp.Value.GetType());
        // combine them into "equals"
        var binaryEqualsExpression = Expression.Equal(propertyExpression, constantExpression);

        if (bodyExpression == null)
        {
            bodyExpression = binaryEqualsExpression;
        }
        else
        {
            // combine each "equals" parts with logical "or"
            bodyExpression = Expression.OrElse(bodyExpression, binaryEqualsExpression);
        }
    }

    // now construct the whole lambda...
    var lambdaExpression = Expression.Lambda<Func<SomeClass, bool>>(bodyExpression, paramExpression);
    // ...and make it useable in .Where()
    var compiledExpression = lambdaExpression.Compile();

    // lets execute in on our test data
    var r = testData.Where(compiledExpression);

    // only #2 should match
    Assert.AreEqual(2, r.Single().Id);
}

更新: 我改变了解决方案:

  1. items 值的类型为 object
  2. constantExpression 尊重值的类型。

这样字典可以包含其他名称-值对和解决方案 仍然有效。字典内容的规则:键必须匹配SomeClass属性名称和值必须匹配给定属性的类型。

【讨论】:

  • 可以将foreach循环替换为Aggregate()
  • 这个解决方案很有意义,看起来还不错,谢谢!但是当我实现它时,我在 var binaryEqualsExpression = Expression.Equal(propertyExpression, constantExpression); 行有一个 System.InvalidOperationException错误是:System.InvalidOperationException:'没有为类型'System.String'和'System.Boolean'定义二元运算符Equal。'我做错了什么或错过了什么?
  • 我更新了解决方案,希望没有错别字,因为这次我没有尝试过:)
  • 上面的 InvalidOperationException 意味着在字典中找到的命名属性之一不是 bool 类型,而是字符串类型。如果您将字典条目的值部分设置为与命名属性的类型匹配,则更新后的解决方案会处理此问题。
【解决方案2】:

执行此操作的简单(但不优雅)的方法是链接一系列Union 语句。您可以使用带有与您的字符串匹配的键和包含适当谓词的值的查找字典。

这里是一个使用扩展方法的例子:

static public IQueryable<Foo> WithFlags(this IQueryable<Foo> source, string[] flags)
{
    var map = new Dictionary<string, Expression<Func<Foo, bool>>>()
    {
        { "Closed", x => x.Closed },
        { "CostFree", x => x.CostFree },
        { "Visible", x => x.Visible }
    };

    //Start with a query that returns nothing
    var query = source.Where(x => false);

    //For each flag supplied by the caller, add an additional set
    foreach (var flag in flags)
    {
        query = query.Union(query.Where(map[flag]));
    }

    return query;
}

使用方法:

var results = DbContext.Foo.WithFlags( new string[] { "Closed", "Visible" }).ToList();

更优雅的方法是构建一个包含Or 逻辑的谓词表达式。这会有点牵扯。我建议寻找第三方工具包。见this answer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-08
    • 1970-01-01
    • 2016-04-07
    • 2021-12-23
    • 2012-05-23
    • 2014-09-12
    相关资源
    最近更新 更多