Включить выражение пути должно ссылаться на свойство навигации - PullRequest
0 голосов
/ 11 июня 2018

Я много искал о своей проблеме, но не нашел четкого решения.Я просто знаю, что не могу использовать выражение «Где linq» с «Включить», но для меня не имеет смысла, как я делаю этот запрос.

var brands = await _context.Brands
            .Include(x => x.FoodCategories
                .Select(y => y.Products
                    .Where(z => z.Sugar)
                    .Select(w => w.FileDetail)))
            .ToListAsync();

На самом деле я хочу применить выражение «Где», ноЯ хочу сущностей в иерархии, как я здесь.Как мне это сделать?Я уже попробовал себя с другим вопросом stackoverflow answer , но я не понимаю суть.Вот мое испытание:

var brands = _context.Brands
            .Select(b => new
            {
                b,
                FoodCategories = b.FoodCategories
                    .Where(x => x.BrandId == b.BrandId)
                    .Select(c => new
                    {
                        c,
                        Products = c.Products
                            .Where(y => y.FoodCategoryId == c.FoodCategoryId &&
                                        y.Sugar)
                            .Select(p => new
                            {
                                p,
                                File = p.FileDetail
                            })
                    })
            })
            .AsEnumerable()
            .Select(z => z.b)
            .ToList();

Но оно не возвращает все товары вместо продуктов, содержащих только сахар.

1 Ответ

0 голосов
/ 11 июня 2018

Почему вы получаете только сахарные продукты .

Но он не возвращает все товары вместо продуктов только из сахара.

Конечно, это.Потому что вы просите дать только сахарные продукты:

var brands = _context.Brands
            .Select(b => new
            {
                b,
                FoodCategories = b.FoodCategories
                    .Where(x => x.BrandId == b.BrandId)
                    .Select(c => new
                    {
                        c,
                        Products = c.Products
                            .Where(y => y.FoodCategoryId == c.FoodCategoryId 
                                        && y.Sugar)                           //HERE!
                            .Select(p => new
                            {
                                p,
                                File = p.FileDetail
                            })
                    })
            })
            .AsEnumerable()
            .Select(z => z.b)
            .ToList();

Если вам нужны все продукты;тогда не фильтруйте только те из них, для которых Sugar имеет значение true.


Здесь много избыточного кода.

b.FoodCategories.Where(x => x.BrandId == b.BrandId)

b.FoodCategories уже выражаеткатегории продуктов питания этой конкретной марки b.Вам не нужно Where.

То же самое относится к

c.Products.Where(y => y.FoodCategoryId == c.FoodCategoryId ... )

Вот улучшенная версия вашего (второго) фрагмента:

var brands = _context.Brands
            .Select(b => new
            {
                b,
                FoodCategories = b.FoodCategories
                    .Select(c => new
                    {
                        c,
                        Products = c.Products
                            .Select(p => new
                            {
                                p,
                                File = p.FileDetail
                            })
                    })
            })
            .AsEnumerable()
            .Select(z => z.b)
            .ToList();

Это должно прояснить, что пользовательскийSelect логика не нужна.Все, что вы делаете, это загружаете связанные сущности в свойства с тем же именем.Вы можете просто положиться на существующие сущности и их отношения, нет смысла снова определять те же отношения.

Единственная причина, по которой пользовательский Select был бы желателен, была, если:

  • Вы хотели ограничить извлеченные столбцы, чтобы уменьшить размер данных (полезно для больших запросов)
  • Вы хотите выборочно загружать дочерние элементы, а не только все связанные дочерние элементы.Ваш код подсказывает, что вы хотите этого, но затем вы говорите «Но он не возвращает все товары» , поэтому я заключаю, что вы не хотите фильтровать товары по содержанию сахара.

Почему ваш Include не работал .

Проще говоря: вы не можете использовать Where операторы во включениях.

Include операторы основаны на структуре сущностей, тогда как Where только фильтрует данные из набора.Один не имеет ничего общего с другим.

И даже если бы вы подумали, что было бы неплохо сделать что-то вроде «включить родителей, только если они имеют активный статус», это просто не то, как Include был разработан для работы.
Include сводится к "для каждого [type1], также загружайте связанные с ним [type2] ".Это будет сделано для каждого [type1] объекта, для которого будет создан ваш запрос, и он загрузит каждого связанного [type2].

Следующим шагом в рефакторингеприведенный выше фрагмент:

var brands = _context.Brands
                    .Include(b => b.FoodCategories)
                    .Include(b => b.FoodCategories.Select(fc => fc.Products))
                    .Include(b => b.FoodCategories.Select(fc => fc.Products.Select(p => p.FileDetail)))
                    .ToList();

Включения дают конкретные инструкции Entity Framework:

  • Для каждого загруженного бренда загрузите соответствующие категории продуктов питания.
  • ДляДля каждой категории загруженных продуктов загрузите соответствующие продукты.
  • Для каждого загруженного продукта загрузите соответствующие данные файла.

Обратите внимание, что не указывает, КАКИМ маркамдолжен быть загружен !Это важное различие, которое нужно сделать.Операторы Include никоим образом не фильтруют данные, они только объясняют, какие дополнительные данные необходимо извлечь для каждой загружаемой записи.
Какие записи будут загружены, не было определеноеще.По умолчанию вы получаете весь набор данных, но вы можете применить дополнительную фильтрацию, используя операторы Where перед загрузкой данных.

Подумайте об этом так:

Ресторан хочетмама каждого нового клиента дает разрешение на подачу десерта клиенту.Поэтому ресторан разрабатывает правило: «каждый клиент должен принести свою мать».
Это эквивалентно db.Customers.Include(c => c.Mother).

. В нем не указано , которое клиентам разрешенопосетить ресторан.В нем только говорится, что любой клиент, который посещает ресторан, должен привести свою мать (если у него нет матери, вместо этого он принесет null).

Обратите внимание, как применяется это правило, независимо от того, какоеклиенты посещают ресторан:

  • Дамская ночь: db.Customers.Include(c => c.Mother).Where(c => c.IsFemale)
  • Родительская ночь: db.Customers.Include(c => c.Mother).Where(c => c.Children.Any())
  • Люди, чьего отца зовут Боб Ночь: db.Customers.Include(c => c.Mother).Where(c => c.Father.Name == "Bob")

Обратите внимание на третий пример.Даже если вы отфильтруете отца, вы загрузите только материнскую сущность.Вполне возможно фильтровать элементы по связанным значениям сущностей без фактической загрузки самих сущностей (отцов).


Вы можете спросить себя «почему Select?».Это хороший вопрос, потому что здесь он не интуитивно понятен.

В идеале вы хотели бы сделать что-то вроде

context.Brand.Include(b => b.FoodCategories.Products.FileDetails)

Но это невозможно из-за ограничений в языке.FoodCategories - это List<FoodCategory>, у которого нет свойства Products.

Однако сам по себе FoodCategory имеет свойство Products.Вот почему используется Select: он позволяет вам получить доступ к свойствам типа элемента списка, а не к самому списку.
Внутренне, EF собирается деконструировать ваш оператор Select (который является Expression) и он выяснит, какое свойство вы хотите загрузить.Не беспокойтесь о том, как EF работает за кулисами.Это не всегда красиво.


Синтаксис Include / Select не самый красивый.Особенно, когда вы углубляетесь в несколько уровней, писать (и читать) становится громоздко.

Поэтому я предлагаю вам инвертировать ваш подход (начните с самого младшего ребенка, изучите до родителя),Технически, он дает тот же результат, но допускает более точный синтаксис Include:

var brands = context.FileDetails
                    .Include(fd => fd.Product)
                    .Include(fd => fd.Product.FoodCategory)
                    .Include(fd => fd.Product.FoodCategory.Brand)
                    .Select(fd => fd.Product.FoodCategory.Brand)

Теперь вам не нужен неприятный обходной путь Select для ссылки на связанные типы.

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

. Обратите внимание, что этот прием действительно работает, только если у вас есть цепочка отношений один-ко-многим.Отношения многие ко многим усложняют применение этого трюка.В худшем случае вам придется прибегнуть к использованию синтаксиса Select из предыдущего примера.


Хотя я не фанат методов Include, которые принимают строковый параметр (мне не нравятся жестко закодированные строки, которые могут потерпеть неудачу при опечатках), я считаю уместным упомянуть здесь, чтоони не страдают от этой проблемы.Если вы используете основанные на строке включения, вы можете делать такие вещи, как:

context.Brands
         .Include("FoodCategories")
         .Include("FoodCategories.Products")
         .Include("FoodCategories.Products.FileDetails")

Логика синтаксического анализа метода включения строки будет автоматически искать элемент внутри List, тем самым эффективно предотвращая уродливый синтаксис.

Но есть и другие причины, по которым я обычно не советую использовать строковые параметры здесь (не обновляется при переименовании свойства, нет intellisense, очень подвержен ошибкам разработчика)

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