Включить, выбрать вложенные объекты, не возвращая - PullRequest
0 голосов
/ 11 мая 2018

Привет, я новичок в LINQ и EF, и я пытаюсь понять, почему приведенный ниже код не возвращает вложенные объекты, даже если я явно загружаю их, используя include

 var x = await _context.AuthorBooks.Where(ub => ub.AuthorId == authorId)
                                .Include(ub => ub.Book)
                                    .ThenInclude (b=> b.Chapters)
                                    .Select(ub => ub.Book).ToListAsync();

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

Я пытаюсь выбрать список всех книг с главами для данного автора, но приведенный выше код возвращает список книг, но без вложенных глав.

Любая помощь?

1 Ответ

0 голосов
/ 14 мая 2018

Объяснение

Вы касаетесь поведения, которое существует в EF.

Проблема заключается в том, как EF обрабатывает загрузку данных. По умолчанию загружаются все скалярные свойства объекта, но не навигационные свойства.

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

Но тогда мы доберемся до Select. Когда вы используете это, вы по существу предоставляете фиксированный список столбцов, которые вы хотите получить. EF ограничится полями, которые вы упомянули.

var x1 = _context.Books.Select(b => b.Name).ToList();

Это приведет к запросу, который извлекает только один столбец.

var x2 = _context.AuthorBooks.Select(ab => ab.Book).ToList();

Поскольку вы ссылаетесь на навигационное свойство (без указания какого-либо конкретного скалярного свойства внутри), EF использует поведение по умолчанию для загрузки всех скалярных свойств навигационного свойства. Запрос извлечет X столбцов (где X - количество скалярных свойств в Book).

var x3 = _context.AuthorBooks.Select(ab => ab.Book.Name).ToList();

Это снова приведет к запросу, который извлекает только один столбец, поскольку вы ссылаетесь на определенное скалярное свойство.


Решение

1. Инвертируйте запрос, чтобы вам не нужно было Select.

Это работает для вашего текущего случая, но не будет работать для всего.

 var x = await _context.Books
                        .Include(b=> b.Chapters)
                        .Where(b => b.AuthorBooks.Any(ab => ab.AuthorId == authorId))
                        .ToListAsync();

2. Выполните Select после извлечения данных.

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

 var x = await _context.AuthorBooks
                            .Include(ub => ub.Book)
                            .ThenInclude(b=> b.Chapters)
                            .Where(ub => ub.AuthorId == authorId)
                            //Fetch the data
                            .ToList()
                            //Now transform the result
                            .Select(ub => ub.Book)
                            .ToListAsync()

3. Явно добавьте нужные данные в Select

 var x = await _context.AuthorBooks.Where(ub => ub.AuthorId == authorId)
                                .Select(ub => new {
                                           Book = ub.Book,
                                           Chapters = ub.Book.Chapters
                                        });
  • Обратите внимание, что вам не нужны операторы Include. Поскольку вы явно указываете EF, что он должен получить, ему не нужно полагаться на неявные инструкции о том, какие навигационные свойства он должен загружать.
  • Вместо любого типа вы можете использовать кортеж или класс. Тип возвращаемого значения зависит от вас, просто убедитесь, что вы явно ссылаетесь на все необходимые данные (скалярные свойства будут автоматически загружаться для всех объектов, на которые ссылаются).

4. Добавьте Include после Select.

Кредиты отправляются в NetMage за первое упоминание в комментариях.

 var x = await _context.AuthorBooks.Where(ub => ub.AuthorId == authorId)
                                .Select(ub => ub.Book)
                                .Include(b => b.Chapters)
                                .ToListAsync();

Обратите внимание, что более ранние включения не нужны, поскольку последующие Select в любом случае переопределяют их.


Вариант 4 - самое чистое решение, на мой взгляд.

Однако вариант 3 лучше, если вы заинтересованы только в подмножестве навигационных свойств. Например, если вы хотите получить только главы с минимальным количеством слов:

 var x = await _context.AuthorBooks.Where(ub => ub.AuthorId == authorId)
                                .Select(ub => new {
                                           Book = ub.Book,
                                           Chapters = ub.Book.Chapters.Where(c => c.WordCount > 1000)
                                        });

Include загружает все связанные свойства. Явный Select дает вам возможность загрузить подмножество связанных свойств, тем самым уменьшая объем данных для передачи.

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