Объяснение
Вы касаетесь поведения, которое существует в 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
дает вам возможность загрузить подмножество связанных свойств, тем самым уменьшая объем данных для передачи.