Поделиться через


Навигации по связям

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

Настройка навигаций

Навигации включаются в модель в рамках настройки связи. То есть по соглашению или использованию HasOneHasManyи т. д. в 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();
}