Навигации по связям
Связи EF Core определяются внешними ключами. Навигации накладываются на внешние ключи, чтобы обеспечить естественное объектно-ориентированное представление для чтения и управления связями. С помощью навигаций приложения могут работать с графами сущностей, не беспокоясь о том, что происходит со значениями внешнего ключа.
Важно!
Несколько связей не могут совместно использовать навигации. Любой внешний ключ может быть связан не более чем с одним переходом от субъекта к зависимым и по крайней мере одним переходом от зависимого к субъекту.
Совет
Нет необходимости делать навигации виртуальными , если они не используются отложенными или отслеживаемыми прокси-серверами отслеживания изменений.
Справочные навигации
Навигации доступны в двух формах и коллекции. Ссылочные навигации — это простые ссылки на объекты к другой сущности. Они представляют "одну" сторону отношений "один ко многим " и "один к одному ". Например:
public Blog TheBlog { get; set; }
Ссылочные навигации должны иметь метод задания, хотя он не должен быть общедоступным. Ссылочные навигации не должны быть автоматически инициализированы в значение по умолчанию, отличное от NULL; это эквивалентно утверждению того, что сущность существует, если она не имеет.
При использовании ссылочных типов, допускающих значение NULL C#, ссылочные навигации должны иметь значение NULL для необязательных связей:
public Blog? TheBlog { get; set; }
Ссылочные навигации для необходимых связей могут иметь значение NULL или не допускающие значения NULL.
Навигации по коллекции
Навигация по коллекции — это экземпляры типа коллекции .NET; то есть любой тип, реализующий ICollection<T>. Коллекция содержит экземпляры связанного типа сущности, из которого может быть любое число. Они представляют "многие" стороны одного ко многим и многим отношениям. Например:
public ICollection<Post> ThePosts { get; set; }
Навигации по коллекции не требуют задания. Обычно инициализировать встроенную коллекцию, тем самым удаляя необходимость когда-либо проверка, если это свойствоnull
. Например:
public ICollection<Post> ThePosts { get; } = new List<Post>();
Совет
Не создавайте случайное свойство выражения, например public ICollection<Post> ThePosts => new List<Post>();
. Это создаст новый пустой экземпляр коллекции при каждом доступе к свойству и, следовательно, будет бесполезным в качестве навигации.
Типы коллекций
Базовый экземпляр коллекции должен реализовывать ICollection<T>и иметь рабочий Add
метод. Обычно используется List<T> или HashSet<T>. List<T>
эффективна для небольших чисел связанных сущностей и поддерживает стабильное упорядочение. HashSet<T>
имеет более эффективные поиски для большого количества сущностей, но не имеет стабильного упорядочения. Вы также можете использовать собственную реализацию пользовательской коллекции.
Важно!
Коллекция должна использовать равенство ссылок. При создании HashSet<T>
навигации по коллекции обязательно используйте ReferenceEqualityComparer.
Массивы нельзя использовать для навигации по коллекции, так как, хотя они реализуют ICollection<T>
, Add
метод создает исключение при вызове.
Несмотря на то, что экземпляр коллекции должен быть экземпляром ICollection<T>
, коллекция не должна быть предоставлена таким образом. Например, обычно предоставляется навигация в виде IEnumerable<T>представления, которое предоставляет представление только для чтения, которое не может быть случайно изменено кодом приложения. Например:
public class Blog
{
public int Id { get; set; }
public IEnumerable<Post> ThePosts { get; } = new List<Post>();
}
Вариант этого шаблона включает методы для управления коллекцией по мере необходимости. Например:
public class Blog
{
private readonly List<Post> _posts = new();
public int Id { get; set; }
public IEnumerable<Post> Posts => _posts;
public void AddPost(Post post) => _posts.Add(post);
}
Код приложения по-прежнему может привести доступную коллекцию к объекту ICollection<T>
, а затем управлять им. Если это проблема, сущность может вернуть защитную копию коллекции. Например:
public class Blog
{
private readonly List<Post> _posts = new();
public int Id { get; set; }
public IEnumerable<Post> Posts => _posts.ToList();
public void AddPost(Post post) => _posts.Add(post);
}
Внимательно рассмотрим, достаточно ли значение, полученное от этого, достаточно высоко, чтобы он перевешивает затраты на создание копии коллекции при каждом доступе к навигации.
Совет
Этот окончательный шаблон работает, так как ПО умолчанию EF обращается к коллекции через его резервное поле. Это означает, что EF добавляет и удаляет сущности из фактической коллекции, а приложения взаимодействуют только с оборонительной копией коллекции.
Инициализация навигаций по коллекции
Навигации по коллекции можно инициализировать с помощью типа сущности.
public class Blog
{
public ICollection<Post> Posts { get; } = new List<Post>();
}
Или лениво:
public class Blog
{
private ICollection<Post>? _posts;
public ICollection<Post> Posts => _posts ??= new List<Post>();
}
Если EF необходимо добавить сущность в навигацию по коллекции, например при выполнении запроса, она инициализирует коллекцию, если она сейчас null
. Созданный экземпляр зависит от предоставленного типа навигации.
- Если навигация предоставляется в виде
HashSet<T>
, создается экземплярHashSet<T>
использования ReferenceEqualityComparer . - В противном случае, если навигация предоставляется в качестве конкретного типа с конструктором без параметров, создается экземпляр этого конкретного типа. Это относится и к
List<T>
другим типам коллекций, включая пользовательские типы коллекций. - В противном случае, если навигация предоставляется как
IEnumerable<T>
объектICollection<T>
, или объектISet<T>
, создается экземплярHashSet<T>
использованияReferenceEqualityComparer
. - В противном случае, если навигация предоставляется как объект
IList<T>
, создается экземплярList<T>
. - В противном случае создается исключение.
Примечание.
Если сущности уведомлений, включая прокси-серверы отслеживания изменений, используются, то ObservableCollection<T> вместо них используются и ObservableHashSet<T> HashSet<T>
используютсяList<T>
.
Важно!
Как описано в документации по отслеживанию изменений, EF отслеживает только один экземпляр любой сущности с заданным значением ключа. Это означает, что коллекции, используемые в качестве навигаций, должны использовать семантику равенства ссылок. По умолчанию типы сущностей, которые не переопределяют равенство объектов. Обязательно используйте ReferenceEqualityComparer при создании HashSet<T>
для использования в качестве навигации, чтобы обеспечить его работу для всех типов сущностей.
Настройка навигаций
Навигации включаются в модель в рамках настройки связи. То есть по соглашению или использованию HasOne
HasMany
и т. д. в API сборки модели. Большая часть конфигурации, связанная с навигацией, выполняется путем настройки самой связи.
Однако существуют некоторые типы конфигурации, относящиеся к самим свойствам навигации, а не являющиеся частью общей конфигурации отношений. Этот тип конфигурации выполняется с Navigation
помощью метода. Например, чтобы принудительно EF получить доступ к навигации через его свойство, а не использовать резервное поле:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Navigation(e => e.Posts)
.UsePropertyAccessMode(PropertyAccessMode.Property);
modelBuilder.Entity<Post>()
.Navigation(e => e.Blog)
.UsePropertyAccessMode(PropertyAccessMode.Property);
}
Примечание.
Вызов Navigation
нельзя использовать для создания свойства навигации. Он используется только для настройки свойства навигации, созданного ранее путем определения связи или соглашения.
Обязательные навигации
Переход от зависимого к субъекту требуется, если требуется связь, что, в свою очередь, означает, что свойство внешнего ключа не допускает значение NULL. И наоборот, навигация является необязательной, если внешний ключ имеет значение NULL, и связь, следовательно, является необязательной.
Ссылочные навигации от субъекта к зависимым отличаются. В большинстве случаев сущность субъекта всегда может существовать без зависимых сущностей. То есть требуемая связь не указывает на то, что всегда будет по крайней мере одна зависимые сущности. В модели EF нет никакого способа, а также нет стандартного способа в реляционной базе данных, чтобы убедиться, что субъект связан с определенным числом зависимых. Если это необходимо, она должна быть реализована в логике приложения (бизнес).
Существует одно исключение из этого правила, когда субъект и зависимые типы совместно используют ту же таблицу в реляционной базе данных или содержатся в документе. Это может произойти с собственными типами или не принадлежащими типами , которыми предоставляется общий доступ к одной таблице. В этом случае свойство навигации от субъекта к зависимому может быть отмечено как обязательное, указывающее, что зависимый должен существовать.
Настройка свойства навигации по мере необходимости выполняется с помощью Navigation
метода. Например:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Navigation(e => e.BlogHeader)
.IsRequired();
}