关于linq:Lambda和嵌套对象

Lambda and Nested Objects

我看了一下这个答案,部分地解决了我的问题。

不过,我需要的是以下内容。

因为我有一个目标;

1
2
3
Product
  string code
  List<suitability> items

然后我有了这个物体;

1
2
3
Suitability
  key
  value

每个产品都有不同数量的[项目],不同产品的键/值对也不同。

在搜索时,我会得到一个合适对象的列表。我现在需要搜索包含(全部)所提供的适用性对象的所有产品。

因此,例如,产品可能具有"牙科=真"和"治疗=真"。

我可能会收到一个要求,所有产品的牙科=真和治疗=假。


首先,我要指出的是,键值对(又称EAV模型)对于表示这种数据来说是一个糟糕的选择,这个问题是一个很好的例子,说明为什么要搜索任意属性集合比搜索特定属性困难得多。

当然,这仍然可以做到:

1
2
3
4
5
6
var suitableProducts =
    from p in products
    where
        p.Items.Any(s => s.Key =="dental" && s.Value =="true") &&
        p.Items.Any(s => s.Key =="therapies" && s.Value =="false")
    select p;

与嵌套属性相比,在实际具有dentaltherapies属性的Product类上进行查询既不容易编写,也不高效。

如果需要查询的项目数可能会发生变化,那么处理这一问题的最简单方法是将过滤器链接在一起:

1
2
3
4
5
6
7
8
9
var searchItems = ...
var result = products;
foreach (var searchItem in searchItems)
{
    result = result.Where(p =>
        p.Items.Any(s => s.Key == searchItem.Key &&
                         s.Value == searchItem.Value));
}
// Do something with result

如果您正在寻找一种更"实用"的方法来实现它而不需要链接,那么:

1
2
3
4
5
var suitabilityConditions = searchItems.Select(i =>
    (Predicate<Product>)(p => p.Items.Any(s =>
        s.Key == searchItem.Key && s.Value == searchItem.Value)));
Predicate<Product> match = p => suitabilityConditions.All(c => c(p));
var suitableProducts = products.Where(match);


使用谓词生成器(来自LinqPad的作者),可以从编译时未知的一组条件构建查询。以下内容应足够:

1
2
3
4
5
6
7
8
9
10
11
var predicate = PredicateBuilder.True<Product>();

foreach (Suitability criteria in searchCriteria)
{
    string tempKey = criteria.Key;
    string tempValue = criteria.Value;
    predicate = predicate.And(p =>
                     p.Items.Any(s => s.Key == tempKey && s.Value == tempValue));
}

return dataContext.Products.Where(predicate.Compile());

更新:这里是我测试过的一些示例代码,它使用IEnumerable作为源代码,结果集productsResult包含products列表中的第一和第二个产品:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var searchCriteria = new List<Suitability>()
    {
        new Suitability() { Key="a", Value="b" },
        new Suitability() { Key="a", Value="c" }
    };

var products = new List<Product>()
    {
        new Product()
            {
                Items = new List<Suitability>() {
                            new Suitability() { Key="a", Value="b" },
                            new Suitability() { Key="a", Value="c" }}
            },
        new Product()
            {
                Items = new List<Suitability>() {
                            new Suitability() { Key="a", Value="b" },
                            new Suitability() { Key="a", Value="c" },
                            new Suitability() { Key="b", Value="c" }}
            },
        new Product()
            {
                Items = new List<Suitability>() {
                            new Suitability() { Key="c", Value="d" }}
            }
    };

    var predicate = PredicateBuilder.True<Product>();

    foreach (Suitability criteria in searchCriteria)
    {
        string tempKey = criteria.Key;
        string tempValue = criteria.Value;
        predicate = predicate.And(p => p.Items.Any(
                         s => s.Key == tempKey && s.Value == tempValue));
    }

    IEnumerable<Product> productsResult = products.Where(predicate.Compile());