Внешние и основные ключи в отношениях

Все связи "один к одному" и "один ко многим " определяются внешним ключом в зависимом конце, который ссылается на первичный или альтернативный ключ в конце субъекта. Для удобства этот первичный или альтернативный ключ называется "основным ключом" для связи. Связи "многие ко многим " состоят из двух связей "один ко многим", каждая из которых определяется внешним ключом, ссылающимся на ключ субъекта.

Совет

Приведенный ниже код можно найти в foreignAndPrincipalKeys.cs.

Внешние ключи;

Свойство или свойства, составляющие внешний ключ, часто обнаруживаются по соглашению. Свойства также можно настроить явным образом с помощью атрибутов сопоставления или в HasForeignKey API сборки модели. HasForeignKey можно использовать с лямбда-выражением. Например, для внешнего ключа, состоящего из одного свойства:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.ContainingBlogId);
}

Или для составного внешнего ключа, состоящего из нескольких свойств:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}

Совет

Использование лямбда-выражений в API сборки модели гарантирует, что использование свойства доступно для анализа кода и рефакторинга, а также предоставляет тип свойства API для использования в дальнейших цепных методах.

HasForeignKey также можно передать имя свойства внешнего ключа в виде строки. Например, для одного свойства:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId");
}

Или для составного внешнего ключа:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId1", "ContainingBlogId2");
}

Использование строки полезно в следующих случаях:

  • Свойство или свойства являются частными.
  • Свойство или свойства не существуют в типе сущности и должны быть созданы в виде теневых свойств.
  • Имя свойства вычисляется или строится на основе некоторых входных данных процесса сборки модели.

Столбцы внешнего ключа, не допускающие значения NULL

Как описано в необязательных и обязательных отношениях, значение NULL свойства внешнего ключа определяет, является ли связь необязательной или обязательной. Однако свойство внешнего ключа, допускающее значение NULL, можно использовать для требуемой связи с помощью атрибута [Required]или вызова IsRequired в API сборки модели. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Или, если внешний ключ обнаруживается по соглашению, IsRequired то его можно использовать без вызова HasForeignKey:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .IsRequired();
}

Конечным результатом этого является то, что столбец внешнего ключа в базе данных не допускает значения NULL, даже если свойство внешнего ключа является пустым. То же самое можно достичь, явно настроив свойство внешнего ключа в соответствии с обязательным требованием. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property(e => e.BlogId)
        .IsRequired();
}

Тени внешних ключей

Свойства внешнего ключа можно создать в виде теневых свойств. Теневое свойство существует в модели EF, но не существует в типе .NET. EF отслеживает значение свойства и состояние внутри страны.

Теневые внешние ключи обычно используются, если существует желание скрыть реляционную концепцию внешнего ключа из модели домена, используемой кодом приложения или бизнес-логикой. Затем этот код приложения полностью управляет связью через навигации.

Совет

Если сущности будут сериализованы, например для отправки по проводу, то значения внешнего ключа могут быть полезным способом сохранения сведений о связи, если сущности не находятся в форме объекта или графа. Поэтому часто прагматично сохранять свойства внешнего ключа в типе .NET для этой цели. Свойства внешнего ключа могут быть закрытыми, что часто является хорошим компромиссом, чтобы избежать предоставления внешнего ключа, позволяя ему перемещаться с сущностью.

Свойства теневых внешних ключей часто создаются соглашением. Внешний ключ тени также будет создан, если аргумент HasForeignKey не соответствует ни одному свойству .NET. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

По соглашению теневой внешний ключ получает его тип из основного ключа в связи. Этот тип допускает значение NULL, если связь не обнаружена как или настроена по мере необходимости.

Свойство теневого внешнего ключа также можно создать явно, что полезно для настройки аспектов свойства. Например, чтобы сделать свойство не допускаемым значением NULL:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property<string>("MyBlogId")
        .IsRequired();

    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Совет

По соглашению свойства внешнего ключа наследуют аспекты, такие как максимальная длина и поддержка Юникода от основного ключа в связи. Поэтому редко необходимо явно настроить аспекты для свойства внешнего ключа.

Создание теневого свойства, если заданное имя не соответствует ни одному свойству типа сущности, можно отключить с помощью ConfigureWarnings. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));

Имена ограничений внешнего ключа

По соглашению ограничения внешнего ключа называются FK_<dependent type name>_<principal type name>_<foreign key property name>. Для составных внешних ключей <foreign key property name> становится разделенным списком имен свойств внешнего ключа подчеркивания.

Это можно изменить в API сборки модели с помощью HasConstraintName. Например:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .HasConstraintName("My_BlogId_Constraint");
}

Совет

Имя ограничения не используется средой выполнения EF. Он используется только при создании схемы базы данных с помощью миграций EF Core.

Индексы для внешних ключей

По соглашению EF создает индекс базы данных для свойства или свойств внешнего ключа. Дополнительные сведения о типах индексов, созданных соглашением, см . в соглашениях о сборке моделей.

Совет

Связи определяются в модели EF между типами сущностей, включенными в эту модель. Некоторые связи могут потребовать ссылки на тип сущности в модели другого контекста, например при использовании шаблона BoundedContext. В таких ситуациях столбцы внешнего ключа должны быть сопоставлены с обычными свойствами, а затем эти свойства можно управлять вручную, чтобы обрабатывать изменения связи.

Ключи субъекта

По соглашению внешние ключи ограничены первичным ключом в конце отношения. Однако вместо этого можно использовать альтернативный ключ. Это достигается с помощью HasPrincipalKey API сборки модели. Например, для одного внешнего ключа свойства:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

Или для составного внешнего ключа с несколькими свойствами:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => new { e.AlternateId1, e.AlternateId2 });
}

HasPrincipalKey можно также передать имя альтернативного свойства ключа в виде строки. Например, для одного ключа свойства:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId");
}

Или для составного ключа:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId1", "AlternateId2");
}

Примечание.

Порядок свойств в основном и внешнем ключе должен соответствовать. Это также порядок определения ключа в схеме базы данных. Он не должен совпадать с порядком свойств в типе сущности или столбцах в таблице.

Для определения альтернативного ключа основной сущности не требуется HasAlternateKey вызывать вызов. Это делается автоматически при HasPrincipalKey использовании с свойствами, которые не являются свойствами первичного ключа. HasAlternateKey Однако можно использовать для дальнейшей настройки альтернативного ключа, например для задания имени ограничения базы данных. Дополнительные сведения см. в разделе "Ключи ".

Связи с бессерверными сущностями

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

Совет

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

Однако типы сущностей без ключей по-прежнему могут иметь внешние ключи и, следовательно, могут выступать в качестве зависимого конца связи. Например, рассмотрим эти типы, где Tag нет ключа:

public class Tag
{
    public string Text { get; set; } = null!;
    public int PostId { get; set; }
    public Post Post { get; set; } = null!;
}

public class Post
{
    public int Id { get; set; }
}

Tag можно настроить в зависимом конце связи:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Tag>()
        .HasNoKey();

    modelBuilder.Entity<Post>()
        .HasMany<Tag>()
        .WithOne(e => e.Post);
}

Примечание.

EF не поддерживает навигации, указывающие на типы сущностей без ключей. См . вопрос GitHub #30331.

Внешние ключи в отношениях "многие ко многим"

В отношениях "многие ко многим" внешние ключи определяются в типе сущности соединения и сопоставляются с ограничениями внешнего ключа в таблице соединения. Все описанное выше можно также применить к этим внешним ключам сущности соединения. Например, задание имен ограничений базы данных:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
            r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}