使用LINQ而不是循环查询嵌套列表

时间:2019-01-24 18:24:22

标签: c# entity-framework linq

让我们说我有以下设置

大陆
-国家
----省
------城市

一个大陆包含许多国家的列表,其中包含许多省的列表,其中包含许多城市的列表。 对于每个嵌套列表,可以说我要检查(名称长度大于5)

代替使用这种循环结构

var countries = dbSet.Countries.Where(c => c.Name.Length > 5);
foreach (var country in countries)
{
   country.Provinces = country.Provinces.Where(p => p.Name.Length > 5);
   foreach (var province in country.Provinces)
   {
      province.Cities = province.Cities.Where(ci => ci.Name.Length() > 5);
   }
}

如何使用LINQ有效地完成相同的任务?

2 个答案:

答案 0 :(得分:2)

有效吗?就书面代码而言,很好,但是我们称其为“干净”。在执行方面,这不是您现在应该问的问题。专注于以可理解的代码完成工作,然后单击“ race your horses”以查看是否确实需要进行改进。

我应该警告的一件事是LINQ是关于查询的,它不会使源序列变异。您正在将过滤后的序列分配回属性,这与LINQ原理相反。标签显示您正在使用Entity Framework,因此这样做绝对不是一个好主意,因为它在后台使用了自己的集合类型。

为回答您的问题,SelectMany扩展方法在投影序列上循环。将其转换为数据库查询后,将转换为联接。

dbSet.Countries
    .Where(c => c.Names.Length > 5)
    .SelectMany(c => c.Provinces)
    .Where(p => p.Name.Length > 5)
    .SelectMany(p => p.Cities)
    .Where(ci => ci.Name.Length > 5)
    .Select(ci => ci.Name);

这将为您提供国家,省和城市名称均超过5个字符的所有城市的名称。

但这只会给您城市名称。如果您想了解每个级别的信息,那么扩展方法将很难使用,因为在此过程的每个步骤中您都必须投影“透明标识符”,并且它会变得很混乱。让编译器使用LINQ语法为您完成此操作。

from c in dbSet.Countries
where c.Name.Length > 5
from p in c.Provinces
where p.Name.Length > 5
from ci in p.Cities
where ci.Name.Length > 5

这将执行与上述相同的操作,除了现在,所有范围变量都通过表达式携带,因此您可以执行以下操作:

select new
{
    CountryName = c.Name,
    ProvinceName = p.Name,
    CityName = ci.Name
};

...或者您想对cpci做的任何事情。

答案 1 :(得分:0)

(我正在将其作为新的答案,以保留接受的答案,同时详细说明评论。)

为了通过查询保留父级,每次循环遍历子对象的集合时,都需要为父级和子级投影一个容器。当您使用LINQ语法时,编译器将以“透明标识符”的形式为您执行此操作。这是透明的,因为您对范围变量的引用“一直通过”并且您从未看到过。 Jon Skeet在Reimplementing LINQ to Objects: Part 19 – Join末尾触及了它们。

为此,您想这次使用SelectMany的不同重载,这也需要一个lambda来投影所需的容器。每次通过子项进行迭代时,都会调用lambda并传递两个参数,即父项和当前迭代的子项。

var result = dbSet.Countries
    .Where(c => c.Names.Length > 5)
    .SelectMany(c => c.Provinces, (c, p) => new { c, p })
    .Where(x1 => x1.p.Name.Length > 5)
    .SelectMany(x1 => x1.p.Cities, (x1, ci) => new { x1.c, x1.p, ci })
    .Where(x2 => x2.ci.Name.Length > 5)
    .Select(x2 => new
    {
        Country = x2.c,
        Province = x2.p,
        City = x2.ci
    })
    .ToList();

x1x2 lambda参数是从上一个SelectMany调用中投射出来的容器。我喜欢称它们为“不透明标识符”。如果您明确引用了它们,它们将不再透明。

cpci范围变量现在是这些容器的属性。

请注意,当您使用let clause时,编译器会做同样的事情,创建一个包含所有可用范围变量的容器。介绍。

最后,我想提一个建议:尽可能使用LINQ语法。因为您没有编译器可以为您做的所有投影,所以编写起来更容易,编写起来也更容易阅读。如果必须诉诸扩展方法,请分部分进行。两种技术可以混合使用。阻止它看起来像一团糟是一种艺术。

相关问题