Fremd- und Prinzipalschlüssel in Beziehungen

Alle 1:1- und 1:N-Beziehungen werden durch einen Fremdschlüssel auf der abhängigen Seite definiert, der auf einen Primär- oder Alternativschlüssel auf der Prinzipalseite verweist. Der Einfachheit halber wird dieser Primär- oder Alternativschlüssel als „Prinzipalschlüssel“ für die Beziehung bezeichnet. M:N-Beziehungen setzen sich aus zwei 1:N-Beziehungen zusammen, die ihrerseits durch einen Fremdschlüssel definiert sind, der auf einen Prinzipalschlüssel verweist.

Tipp

Der folgende Code befindet sich in ForeignAndPrincipalKeys.cs.

Fremdschlüssel

Die Eigenschaft oder Eigenschaften, aus denen Fremdschlüssel bestehen, werden häufig gemäß Konvention erkannt. Die Eigenschaften können auch explizit konfiguriert werden, entweder mithilfe von Zuordnungsattributen oder mit HasForeignKey in der Modellerstellungs-API. HasForeignKey kann mit einem Lambdaausdruck verwendet werden. Beispiel für einen Fremdschlüssel, der aus einer einzelnen Eigenschaft besteht:

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

Oder für einen aus mehreren Eigenschaften zusammengesetzten Fremdschlüssel:

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

Tipp

Mithilfe von Lambdaausdrücken in der Modellerstellungs-API wird sichergestellt, dass die Verwendung der Eigenschaft für die Codeanalyse und das Refactoring zur Verfügung steht. Außerdem wird der Eigenschaftstyp der API zur Verwendung in weiteren verketteten Methoden zur Verfügung gestellt.

HasForeignKey kann auch der Name der Fremdschlüsseleigenschaft als Zeichenfolge übergeben werden. Beispielsweise für eine einzelne Eigenschaft:

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

Oder für einen zusammengesetzten Fremdschlüssel:

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

Die Verwendung einer Zeichenfolge ist in folgenden Situationen nützlich:

  • Die Eigenschaften sind privat.
  • Die Eigenschaften sind für den Entitätstyp nicht vorhanden und sollten als Schatteneigenschaften erstellt werden.
  • Der Eigenschaftsname wird auf der Grundlage einiger Eingaben bei der Modellerstellung berechnet oder konstruiert.

Non-Nullable-Fremdschlüsselspalten

Wie in Optionale und erforderliche Beziehungen beschrieben, bestimmt die NULL-Zulässigkeit der Fremdschlüsseleigenschaft, ob eine Beziehung optional oder erforderlich ist. Eine Nullwerte zulassende Fremdschlüsseleigenschaft kann jedoch mithilfe des [Required]Attributs oder durch den Aufruf von IsRequired in der Modellerstellungs-API für eine erforderliche Beziehung verwendet werden. Beispiel:

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

Oder, wenn der Fremdschlüssel gemäß Konvention ermittelt wird, kann IsRequired ohne einen Aufruf von HasForeignKey verwendet werden:

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

Das Endergebnis ist, dass die Fremdschlüsselspalte in der Datenbank als „Non-Nullable“ festgelegt wird, auch wenn die Fremdschlüsseleigenschaft Nullwerte zulässt. Dasselbe kann erreicht werden, indem die Fremdschlüsseleigenschaft selbst explizit als erforderlich konfiguriert wird. Beispiel:

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

Schattenfremdschlüssel

Fremdschlüsseleigenschaften können als Schatteneigenschaften erstellt werden. Eine Schatteneigenschaft existiert im EF-Modell, aber nicht für den .NET-Typ. EF verfolgt den Eigenschaftswert und -zustand intern nach.

Schattenfremdschlüssel werden in der Regel verwendet, wenn Sie das relationale Konzept eines Fremdschlüssels vor dem Domänenmodell verbergen möchten, das vom Anwendungscode/der Geschäftslogik verwendet wird. Dieser Anwendungscode bearbeitet dann die Beziehung vollständig über Navigationen.

Tipp

Wenn Entitäten serialisiert werden sollen, z. B. um sie über eine Leitung zu versenden, dann können die Fremdschlüsselwerte eine nützliche Möglichkeit sein, die Beziehungsinformationen intakt zu halten, wenn die Entitäten nicht in einer Objekt-/Graphenform vorliegen. Es ist daher oft pragmatisch, zu diesem Zweck Fremdschlüsseleigenschaften im .NET-Typ zu behalten. Fremdschlüsseleigenschaften können privat sein, was oft ein guter Kompromiss ist, um zu vermeiden, dass der Fremdschlüssel offengelegt wird, während sein Wert bei der Entität verbleiben kann.

Schattenfremdschlüssel-Eigenschaften werden oft gemäß Konvention erstellt. Ein Schattenfremdschlüssel wird auch erstellt, wenn das Argument zu HasForeignKey keiner .NET-Eigenschaft entspricht. Beispiel:

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

Gemäß Konvention erhält ein Schattenfremdschlüssel seinen Typ vom Prinzipalschlüssel der Beziehung. Dieser Typ wird als „Nullwerte zulassend“ festgelegt, es sei denn, die Beziehung wird als erforderlich erkannt oder entsprechend konfiguriert.

Die Schattenfremdschlüssel-Eigenschaft kann auch explizit erstellt werden, was für die Konfiguration von Facetten der Eigenschaft nützlich ist. So kann z. B. die Eigenschaft als „Non-Nullable“ festgelegt werden

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

Tipp

Gemäß Konvention erben Fremdschlüsseleigenschaften Facetten wie die maximale Länge und die Unicode-Unterstützung vom Prinzipalschlüssel in der Beziehung. Es ist daher selten notwendig, Facetten für eine Fremdschlüsseleigenschaft explizit zu konfigurieren.

Die Erstellung einer Schatteneigenschaft, wenn der angegebene Name mit keiner Eigenschaft des Entitätstyps übereinstimmt, kann mithilfe von ConfigureWarnings deaktiviert werden. Beispiel:

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

Namen für Fremdschlüsseleinschränkungen

Fremdschlüsseleinschränkungen werden gemäß Konvention mit FK_<dependent type name>_<principal type name>_<foreign key property name> bezeichnet. Bei zusammengesetzten Fremdschlüsseln wird <foreign key property name> zu einer durch Unterstriche getrennten Liste von Namen von Fremdschlüsseleigenschaften.

Dies kann in der Modellerstellungs-API mithilfe von HasConstraintName geändert werden. Beispiel:

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

Tipp

Der Einschränkungsname wird von der EF-Runtime nicht verwendet. Er wird nur verwendet, wenn Sie ein Datenbankschema mithilfe von EF Core-Migrationen erstellen.

Indizes für Fremdschlüssel

Gemäß Konvention erstellt EF einen Datenbankindex für die Eigenschaft oder Eigenschaften eines Fremdschlüssels. Weitere Informationen zu den typen von Indizes, die gemäß Konvention erstellt wurden, finden Sie unter Konventionen für die Modellerstellung.

Tipp

Beziehungen werden im EF-Modell zwischen Entitätstypen definiert, die in diesem Modell enthalten sind. Einige Beziehungen müssen möglicherweise auf einen Entitätstyp im Modell eines anderen Kontexts verweisen, wenn Sie z. B. das BoundedContext-Muster verwenden. In diesen Fällen sollte(n) die Fremdschlüsselspalte(n) normalen Eigenschaften zugeordnet werden. Diese Eigenschaften können dann manuell bearbeitet werden, um Änderungen an der Beziehung zu verarbeiten.

Prinzipalschlüssel

Gemäß Konvention sind Fremdschlüssel auf den Primärschlüssel am Prinzipalende der Beziehung beschränkt. Sie können jedoch stattdessen auch einen Alternativschlüssel verwenden. Dies wird mithilfe von HasPrincipalKey für die Modellerstellungs-API erreicht. Beispielsweise für einen Fremdschlüssel mit einzelner Eigenschaft:

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

Oder für einen zusammengesetzten Fremdschlüssel mit mehreren Eigenschaften:

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 kann auch der Name der Alternativschlüsseleigenschaft als Zeichenfolge übergeben werden. Beispielsweise für einen einzelnen Eigenschaftsschlüssel:

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

Oder für einen zusammengesetzten Schlüssel:

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

Hinweis

Die Reihenfolge der Eigenschaften im Prinzipal- und Fremdschlüssel muss übereinstimmen. Dies ist auch die Reihenfolge, in der der Schlüssel im Datenbankschema definiert ist. Sie muss nicht mit der Reihenfolge der Eigenschaften im Entitätstyp oder den Spalten in der Tabelle übereinstimmen.

Es ist nicht erforderlich, HasAlternateKey aufzurufen, um den Alternativschlüssel für die Prinzipalentität zu definieren. Dies erfolgt automatisch, wenn HasPrincipalKey mit Eigenschaften verwendet wird, die nicht den Primärschlüsseleigenschaften entsprechen. Sie können jedoch HasAlternateKey verwenden, um den Alternativschlüssel weiter zu konfigurieren, um z. B. seinen Datenbankeinschränkungsnamen festzulegen. Weitere Informationen finden Sie unter Schlüssel.

Beziehungen zu schlüssellosen Entitäten

Jede Beziehung muss über einen Fremdschlüssel verfügen, der auf einen Prinzipalschlüssel (Primär- oder Alternativschlüssel) verweist. Dies bedeutet, dass ein schlüsselloser Entitätstyp nicht als Prinzipalende einer Beziehung fungieren kann, da es keinen Prinzipal gibt, auf den die Fremdschlüssel verweisen können.

Tipp

Ein Entitätstyp kann keinen Alternativschlüssel, aber einen Primärschlüssel aufweisen. In diesem Fall muss der Alternativschlüssel (oder einer der Alternativschlüssel, wenn es mehrere gibt) zum Primärschlüssel hochgestuft werden.

Für schlüssellose Entitäten können weiterhin Fremdschlüssel definiert sein, sodass sie als abhängiges Ende einer Beziehung fungieren können. Betrachten wir z. B. diese Typen, bei denen Tag keinen Schlüssel aufweist:

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 kann auf der abhängigen Seite der Beziehung konfiguriert werden:

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

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

Hinweis

EF unterstützt keine Navigationen, die auf schlüssellose Entitätstypen verweisen. Weitere Informationen finden Sie unter GitHub-Issue #30331.

Fremdschlüssel in M:N-Beziehungen

In M:N-Beziehungen werden die Fremdschlüssel hinsichtlich des Typs der Joinentität definiert und zu Fremdschlüsseleinschränkungen in der Jointabelle zugeordnet. Alle oben beschriebenen Informationen können auch auf die Fremdschlüssel dieser Joinentität angewendet werden. Beispielsweise das Festlegen der Namen der Datenbankeinschränkungen:

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