Запросы к вложенным спискам с помощью LINQ вместо циклов - PullRequest
0 голосов
/ 24 января 2019

Допустим, у меня есть следующие настройки

Континент
- Страны
---- Провинции
------ Города

Континент содержитсписок многих стран, который содержит список многих провинций, который содержит список многих городов.Для каждого вложенного списка скажем, что я хочу сделать проверку (длина имени больше 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 голосов
/ 25 января 2019

(я делаю этот новый ответ, чтобы сохранить принятый ответ при подробном рассмотрении комментария.)

Чтобы сохранить родительские уровни в запросе, вам необходимо проецировать контейнер для родительского и дочернего элементов каждый раз, когда вы просматриваете коллекцию дочерних объектов. Когда вы используете синтаксис LINQ, компилятор делает это за вас в форме «прозрачного идентификатора». Это прозрачно, потому что ваши ссылки на переменные диапазона «проходят сквозь» это, и вы никогда не видите это. Джон Скит касается их в конце Повторная реализация LINQ для объектов: часть 19 - Присоединение .

Для этого вы хотите использовать другую перегрузку SelectMany на этот раз, которая также использует лямбду для проецирования нужного вам контейнера. Каждая итерация через дочерние элементы, эта лямбда вызывается и передается два параметра, родительский элемент и дочерний элемент текущей итерации.

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();

Лямбда-аргументы x1 и x2 - это контейнеры, спроецированные из предыдущего вызова SelectMany. Мне нравится называть их "непрозрачными идентификаторами". Они больше не прозрачны, если вы обращаетесь к ним явно.

Переменные диапазона c, p и ci теперь являются свойствами этих контейнеров.

В качестве бонуса, когда вы используете let предложение , компилятор делает то же самое, создавая контейнер со всеми доступными переменными диапазона и . вводится новая переменная.

Я хочу закончить этим советом: максимально используйте синтаксис LINQ. Его легче писать и понимать правильно, и его легче читать, потому что у вас нет всех тех проекций, которые компилятор может сделать для вас. Если вам приходится прибегать к методам расширения, делайте это по частям. Эти две техники могут быть смешаны. Есть искусство не дать ему выглядеть как беспорядок.

0 голосов
/ 24 января 2019

Эффективно?С точки зрения написанного кода, хорошо, но мы будем называть это «чисто».С точки зрения исполнения, это не тот вопрос, который вам следует задавать в данный момент.Сосредоточьтесь на том, чтобы выполнить работу в понятном коде, а затем " мчитесь на лошадях ", чтобы увидеть, действительно ли вам нужно улучшить его.запрос, который не изменяет исходные последовательности.Вы присваиваете отфильтрованные последовательности обратно свойствам, что противоречит принципам 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
};

... или что вы хотите сделать с c, p и ci.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...