Какие бывают связи между сущностями в Entity Framework
Что проверяют
Понимаете ли вы, как связь из модели превращается в таблицы и внешние ключи и какая сторона в паре главная. Следом обычно спрашивают про загрузку связанных данных: Include, ленивую загрузку и проблему N+1.
Как отвечать
Говорите в терминах EF Core, классический EF6 сейчас встречается в основном в легаси на .NET Framework:
- один ко многим: самый частый случай. Внешний ключ лежит в зависимой сущности (
Book.AuthorId), у главной коллекцияICollection<Book>. Во Fluent API:HasOne(b => b.Author).WithMany(a => a.Books).HasForeignKey(b => b.AuthorId); - один к одному: EF Core должен знать, какая сторона зависимая, поэтому ключ указывают с типом:
HasForeignKey<Profile>(p => p.UserId). На внешний ключ создаётся уникальный индекс, без него в базе это был бы обычный один ко многим; - многие ко многим: начиная с EF Core 5 достаточно двух коллекций, промежуточную таблицу EF создаст сам. Если у связи есть свои данные (дата записи студента на курс), промежуточную сущность описывают явно или настраивают через
UsingEntity; - ссылка на себя: иерархии вроде дерева категорий или сотрудника с руководителем;
- обязательная и необязательная: зависит от nullability внешнего ключа,
intпротивint?, и определяет поведение при удалении главной записи.
Настраивают связи тремя способами: соглашениями по именам, атрибутами [ForeignKey] и [InverseProperty] и Fluent API в OnModelCreating.
Про загрузку: жадная через Include и ThenInclude, явная через Entry(...).Collection(...).Load() и ленивая, которая в EF Core требует пакета Microsoft.EntityFrameworkCore.Proxies, вызова UseLazyLoadingProxies() и virtual у навигационных свойств.
Где спотыкаются
Ставят virtual на все навигационные свойства по привычке из EF6 и думают, что ленивая загрузка включилась. Без прокси в EF Core она не работает, и навигация просто остаётся null.
Не замечают N+1: в цикле обращаются к навигационному свойству, и при ленивой загрузке на каждую строку уходит отдельный запрос.
Делают Include нескольких коллекций в одном запросе и получают декартов взрыв строк. Здесь помогает AsSplitQuery().