Klucze obce i główne w relacjach

Wszystkie relacje jeden do jednego i jeden do wielu są definiowane przez klucz obcy na końcu zależnym, który odwołuje się do klucza podstawowego lub alternatywnego na końcu podmiotu zabezpieczeń. Dla wygody ten klucz podstawowy lub alternatywny jest znany jako "klucz główny" dla relacji. Relacje wiele-do-wielu składają się z dwóch relacji jeden do wielu, z których każda jest definiowana przez klucz obcy odwołujące się do klucza głównego.

Napiwek

Poniższy kod można znaleźć w pliku ForeignAndPrincipalKeys.cs.

Klucze obce

Właściwość lub właściwości tworzące klucz obcy są często odnajdywane zgodnie z konwencją. Właściwości można również skonfigurować jawnie przy użyciu atrybutów mapowania lub HasForeignKey interfejsu API tworzenia modelu. HasForeignKey można używać z wyrażeniem lambda. Na przykład w przypadku klucza obcego złożonego z jednej właściwości:

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

Lub dla złożonego klucza obcego składającego się z więcej niż jednej właściwości:

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

Napiwek

Użycie wyrażeń lambda w interfejsie API tworzenia modelu zapewnia, że użycie właściwości jest dostępne do analizy kodu i refaktoryzacji, a także udostępnia typ właściwości interfejsowi API do użycia w dalszych metodach łańcuchowych.

HasForeignKey można również przekazać nazwę właściwości klucza obcego jako ciąg. Na przykład dla jednej właściwości:

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

Lub dla złożonego klucza obcego:

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

Użycie ciągu jest przydatne w następujących przypadkach:

  • Właściwość lub właściwości są prywatne.
  • Właściwość lub właściwości nie istnieją w typie jednostki i powinny zostać utworzone jako właściwości w tle.
  • Nazwa właściwości jest obliczana lub konstruowana na podstawie niektórych danych wejściowych procesu tworzenia modelu.

Kolumny klucza obcego bez wartości null

Zgodnie z opisem w temacie Opcjonalne i wymagane relacje, wartość null właściwości klucza obcego określa, czy relacja jest opcjonalna, czy wymagana. Można jednak użyć właściwości klucza obcego dopuszczającego wartość null dla wymaganej relacji przy użyciu atrybutu [Required]lub wywołania IsRequired w interfejsie API tworzenia modelu. Na przykład:

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

Ewentualnie, jeśli klucz obcy zostanie odnaleziony zgodnie z konwencją, można go IsRequired użyć bez wywołania metody HasForeignKey:

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

Wynikiem końcowym jest to, że kolumna klucza obcego w bazie danych jest niepusta, nawet jeśli właściwość klucza obcego ma wartość null. Można to zrobić, jawnie konfigurując samą właściwość klucza obcego zgodnie z potrzebami. Na przykład:

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

Klucze obce w tle

Właściwości klucza obcego można utworzyć jako właściwości w tle. Właściwość w tle istnieje w modelu EF, ale nie istnieje w typie platformy .NET. Program EF śledzi wartość właściwości i stan wewnętrznie.

Klucze obce w tle są zwykle używane, gdy istnieje chęć ukrycia relacyjnego pojęcia klucza obcego z modelu domeny używanego przez kod aplikacji/logikę biznesową. Następnie ten kod aplikacji manipuluje relacją w całości za pośrednictwem nawigacji.

Napiwek

Jeśli jednostki będą serializowane, na przykład w celu wysłania za pośrednictwem przewodu, wartości klucza obcego mogą być przydatnym sposobem przechowywania informacji o relacji bez zmian, gdy jednostki nie znajdują się w formularzu obiektu/grafu. Dlatego często pragmatyczne zachowanie właściwości klucza obcego w typie platformy .NET w tym celu. Właściwości klucza obcego mogą być prywatne, co jest często dobrym kompromisem, aby uniknąć ujawnienia klucza obcego, pozwalając jednocześnie na podróż z jednostką.

Właściwości klucza obcego w tle są często tworzone zgodnie z konwencją. Jeśli argument nie pasuje do HasForeignKey żadnej właściwości platformy .NET, zostanie również utworzony klucz obcy w tle. Na przykład:

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

Zgodnie z konwencją klucz obcy w tle pobiera jego typ z klucza głównego w relacji. Ten typ jest dopuszczany do wartości null, chyba że relacja zostanie wykryta jako wymagana lub skonfigurowana.

Właściwość klucza obcego w tle można również utworzyć jawnie, co jest przydatne do konfigurowania aspektów właściwości. Aby na przykład ustawić właściwość niepustą:

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");
}

Napiwek

Zgodnie z konwencją właściwości klucza obcego dziedziczą aspekty, takie jak maksymalna długość i obsługa Unicode z klucza głównego w relacji. W związku z tym rzadko konieczne jest jawne skonfigurowanie aspektów dla właściwości klucza obcego.

Utworzenie właściwości w tle, jeśli podana nazwa nie jest zgodna z żadną właściwością typu jednostki, można wyłączyć przy użyciu polecenia ConfigureWarnings. Na przykład:

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

Nazwy ograniczeń klucza obcego

Zgodnie z konwencją ograniczenia klucza obcego mają nazwę FK_<dependent type name>_<principal type name>_<foreign key property name>. W przypadku złożonych kluczy <foreign key property name> obcych staje się podkreślenia oddzieloną listą nazw właściwości klucza obcego.

Można to zmienić w interfejsie API tworzenia modelu przy użyciu polecenia HasConstraintName. Na przykład:

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");
}

Napiwek

Nazwa ograniczenia nie jest używana przez środowisko uruchomieniowe EF. Jest on używany tylko podczas tworzenia schematu bazy danych przy użyciu migracji platformy EF Core.

Indeksy kluczy obcych

Zgodnie z konwencją program EF tworzy indeks bazy danych dla właściwości lub właściwości klucza obcego. Aby uzyskać więcej informacji na temat typów indeksów utworzonych zgodnie z konwencją, zobacz Konwencje tworzenia modeli.

Napiwek

Relacje są definiowane w modelu EF między typami jednostek uwzględnionych w tym modelu. Niektóre relacje mogą wymagać odwołowania się do typu jednostki w modelu innego kontekstu — na przykład w przypadku używania wzorca BoundedContext. W takiej sytuacji kolumny kluczy obcych powinny być mapowane na normalne właściwości, a te właściwości można manipulować ręcznie w celu obsługi zmian w relacji.

Klucze podmiotu zabezpieczeń

Zgodnie z konwencją klucze obce są ograniczone do klucza podstawowego na końcu głównej relacji. Zamiast tego można użyć alternatywnego klucza. Jest to realizowane przy użyciu HasPrincipalKey interfejsu API tworzenia modelu. Na przykład dla pojedynczego klucza obcego właściwości:

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

Lub dla złożonego klucza obcego z wieloma właściwościami:

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 można również przekazać nazwę właściwości klucza alternatywnego jako ciąg. Na przykład dla pojedynczego klucza właściwości:

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

Lub dla klucza złożonego:

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

Uwaga

Kolejność właściwości w kluczu podmiotu zabezpieczeń i klucza obcego musi być zgodna. Jest to również kolejność, w której klucz jest zdefiniowany w schemacie bazy danych. Nie musi być taka sama jak kolejność właściwości w typie jednostki lub kolumnach w tabeli.

Nie ma potrzeby wywoływania HasAlternateKey w celu zdefiniowania klucza alternatywnego w jednostce głównej. Odbywa się to automatycznie, gdy HasPrincipalKey jest używane z właściwościami, które nie są właściwościami klucza podstawowego. HasAlternateKey Można jednak użyć do dalszej konfiguracji klucza alternatywnego, takiego jak ustawienie jego nazwy ograniczenia bazy danych. Aby uzyskać więcej informacji, zobacz Klucze .

Relacje z jednostkami bez klucza

Każda relacja musi mieć klucz obcy, który odwołuje się do klucza głównego (podstawowego lub alternatywnego). Oznacza to, że typ jednostki bez klucza nie może działać jako główny koniec relacji, ponieważ nie ma klucza głównego dla kluczy obcych do odwołania.

Napiwek

Typ jednostki nie może mieć klucza alternatywnego, ale nie ma klucza podstawowego. W takim przypadku klucz alternatywny (lub jeden z kluczy alternatywnych, jeśli istnieje kilka) musi zostać podwyższony do klucza podstawowego.

Jednak typy jednostek bez klucza mogą nadal mieć zdefiniowane klucze obce, a tym samym mogą działać jako zależny koniec relacji. Rozważmy na przykład następujące typy, w których Tag nie ma klucza:

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 można skonfigurować na zależnym końcu relacji:

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

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

Uwaga

Platforma EF nie obsługuje nawigacji wskazujących typy jednostek bez klucza. Zobacz Problem z usługą GitHub #30331.

Klucze obce w relacjach wiele-do-wielu

W relacjach wiele-do-wielu klucze obce są definiowane w typie jednostki sprzężenia i mapowane na ograniczenia klucza obcego w tabeli sprzężenia. Wszystkie opisane powyżej elementy można również zastosować do tych kluczy obcych jednostki sprzężenia. Na przykład ustawienie nazw ograniczeń bazy danych:

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"));
}