基于字典元素的动态 lambda 参数

Dynamic lambda parameters based on dictionary elements

我的计划是创建一个查询,但参数基于一个字典。 字典包含字符串键和布尔值。 可以是字典中的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 个项目。

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

这样做的简单(但不够优雅)的方法是链接一系列 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

可以通过 System.Linq.Expressions.Expression class 上公开的静态方法轻松构建 LINQ 表达式。 这是一个包含您的需求的示例,假设您要针对命名 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 属性 名称和值必须匹配给定的 属性 类型。