Freigeben über


Konventionen für die Beziehungsermittlung

EF Core verwendet eine Reihe von Konventionen beim Ermitteln und Erstellen eines Modells basierend auf Entitätstypklassen. Dieses Dokument fasst die Konventionen zusammen, die zum Ermitteln und Konfigurieren von Beziehungen zwischen Entitätstypen verwendet werden.

Von Bedeutung

Die hier beschriebenen Konventionen können durch explizite Konfiguration der Beziehung mithilfe von Zuordnungsattributen oder der Modellerstellungs-API außer Kraft gesetzt werden.

Tipp

Der folgende Code finden Sie in RelationshipConventions.cs.

Navigation entdecken

Die Beziehungsermittlung beginnt mit der Ermittlung von Navigationen zwischen Entitätstypen .

Referenznavigationen

Eine Eigenschaft eines Entitätstyps wird als Referenznavigation erkannt, wenn:

  • Das Eigentum ist öffentlich.
  • Die Eigenschaft verfügt über einen Getter und einen Setter.
  • Der Eigenschaftstyp ist oder könnte ein Entitätstyp sein. Dies bedeutet, dass der Typ
    • Muss ein Verweistyp sein.
    • Darf nicht explizit als primitiver Eigenschaftstyp konfiguriert worden sein.
    • Darf nicht vom verwendeten Datenbankanbieter als primitiver Eigenschaftstyp zugeordnet werden.
    • Darf nicht automatisch in einen primitiven Eigenschaftstyp konvertiert werden, der vom verwendeten Datenbankanbieter zugeordnet wird.
  • Die Eigenschaft ist nicht statisch.
  • Die Eigenschaft ist keine Indexereigenschaft.

Betrachten Sie beispielsweise die folgenden Entitätstypen:

public class Blog
{
    // Not discovered as reference navigations:
    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public Uri? Uri { get; set; }
    public ConsoleKeyInfo ConsoleKeyInfo { get; set; }
    public Author DefaultAuthor => new() { Name = $"Author of the blog {Title}" };

    // Discovered as a reference navigation:
    public Author? Author { get; private set; }
}

public class Author
{
    // Not discovered as reference navigations:
    public Guid Id { get; set; }
    public string Name { get; set; } = null!;
    public int BlogId { get; set; }

    // Discovered as a reference navigation:
    public Blog Blog { get; init; } = null!;
}

Bei diesen Typen werden Blog.Author und Author.Blog als Referenznavigationen ermittelt. Andererseits werden die folgenden Eigenschaften nicht als Referenznavigationen identifiziert.

  • Blog.Id, da int ein zugeordneter Grundtyp ist
  • Blog.Title, da 'string' ein abgebildeter primitiver Typ ist
  • Blog.Uri, da Uri automatisch in einen zugeordneten Grundtyp konvertiert wird
  • Blog.ConsoleKeyInfo, da es sich um ConsoleKeyInfo einen C#-Werttyp handelt
  • Blog.DefaultAuthor, da die Eigenschaft keinen Setter hat
  • Author.Id, da Guid ein zugeordneter Grundtyp ist
  • Author.Name, da 'string' ein abgebildeter primitiver Typ ist
  • Author.BlogId, da int ein zugeordneter Grundtyp ist

Navigationsmenüs für Sammlungen

Eine Eigenschaft eines Entitätstyps wird als Sammlungsnavigation erkannt, wenn:

  • Das Eigentum ist öffentlich.
  • Die Eigenschaft verfügt über einen Getter. Sammlungsnavigationen können Setter aufweisen, dies ist jedoch nicht erforderlich.
  • Der Eigenschaftstyp ist oder implementiert IEnumerable<TEntity>, wobei TEntity ein Entitätstyp ist oder sein könnte. Dies bedeutet, dass es sich bei TEntity um den Typ handelt:
    • Muss ein Verweistyp sein.
    • Darf nicht explizit als primitiver Eigenschaftstyp konfiguriert worden sein.
    • Darf nicht vom verwendeten Datenbankanbieter als primitiver Eigenschaftstyp zugeordnet werden.
    • Darf nicht automatisch in einen primitiven Eigenschaftstyp konvertiert werden, der vom verwendeten Datenbankanbieter zugeordnet wird.
  • Die Eigenschaft ist nicht statisch.
  • Die Eigenschaft ist keine Indexereigenschaft.

Im folgenden Code werden beispielsweise sowohl Blog.Tags als auch Tag.Blogs als Sammlungsnavigationen erkannt.

public class Blog
{
    public int Id { get; set; }
    public List<Tag> Tags { get; set; } = null!;
}

public class Tag
{
    public Guid Id { get; set; }
    public IEnumerable<Blog> Blogs { get; } = new List<Blog>();
}

Kopplungsnavigationen

Sobald eine Navigation von z. B. Entitätstyp A zu Entitätstyp B ermittelt wird, muss als Nächstes festgestellt werden, ob in die entgegengesetzte Richtung eine Umkehrung vorliegt – d. h. vom Entitätstyp B zu Entitätstyp A. Wenn eine solche Umkehrung gefunden wird, werden die beiden Navigationen zusammengeführt, um eine einzelne, bidirektionale Beziehung zu bilden.

Der Beziehungstyp wird bestimmt durch die Frage, ob es sich bei der Navigation und ihrer Umkehrung um eine Referenz- oder eine Sammelnavigation handelt. Dies gilt insbesondere in folgenden Fällen:

  • Wenn eine Navigation eine Sammlungsnavigation ist und die andere eine Referenznavigation ist, dann ist die Beziehung eins-zu-viele.
  • Wenn beide Navigationen Referenznavigationen sind, ist die Beziehung 1:1.
  • Wenn beide Navigationen Sammlungsnavigationen sind, ist die Beziehung viele-zu-viele.

Die Ermittlung jeder dieser Beziehungstypen wird in den folgenden Beispielen gezeigt:

Eine einzelne 1:n-Beziehung wird zwischen Blog und Post durch das Koppeln der Navigationspunkte Blog.Posts und Post.Blog ermittelt:

public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; }
    public Blog? Blog { get; set; }
}

Eine einzelne 1:1-Beziehung zwischen Blog und Author wurde durch Kopplung der Blog.Author- und Author.Blog-Navigationen ermittelt.

public class Blog
{
    public int Id { get; set; }
    public Author? Author { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public int? BlogId { get; set; }
    public Blog? Blog { get; set; }
}

Eine einzelne Viele-zu-viele-Beziehung zwischen Post und Tag wird durch das Koppeln der Navigationen Post.Tags und Tag.Posts ermittelt.

public class Post
{
    public int Id { get; set; }
    public ICollection<Tag> Tags { get; } = new List<Tag>();
}

public class Tag
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

Hinweis

Diese Kopplung von Navigationen ist möglicherweise falsch, wenn die beiden Navigationselemente zwei, unterschiedliche, unidirektionale Beziehungen darstellen. In diesem Fall müssen die beiden Beziehungen explizit konfiguriert werden.

Die Kopplung von Beziehungen funktioniert nur, wenn es eine einzelne Beziehung zwischen zwei Typen gibt. Mehrere Beziehungen zwischen zwei Typen müssen explizit konfiguriert werden.

Hinweis

Die hier beschriebenen Beschreibungen beziehen sich auf Beziehungen zwischen zwei verschiedenen Typen. Es ist jedoch möglich, dass sich derselbe Typ auf beiden Enden einer Beziehung befindet, und daher für einen einzelnen Typ zwei Navigationselemente miteinander gekoppelt sind. Dies wird als selbstverweisende Beziehung bezeichnet.

Ermitteln von Fremdschlüsseleigenschaften

Sobald die Navigationen für eine Beziehung entweder erkannt oder explizit konfiguriert wurden, werden diese Navigationen verwendet, um geeignete Fremdschlüsseleigenschaften für die Beziehung zu ermitteln. Eine Eigenschaft wird als Fremdschlüssel erkannt, wenn:

  • Der Eigenschaftentyp ist mit dem primären oder alternativen Schlüssel des Prinzipalentitätstyps kompatibel.
    • Typen sind kompatibel, wenn sie identisch sind oder der Fremdschlüsseleigenschaftstyp eine nullable Version des Primär- oder alternativen Schlüsseleigenschaftstyps ist.
  • Der Eigenschaftsname entspricht einer der Benennungskonventionen für eine Fremdschlüsseleigenschaft. Die Benennungskonventionen sind:
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity type name><principal key property name>
    • <principal entity type name>Id
  • Wenn das abhängige Ende explizit mithilfe der Modellerstellungs-API konfiguriert wurde und der abhängige Primärschlüssel kompatibel ist, wird der abhängige Primärschlüssel auch als Fremdschlüssel verwendet.

Tipp

Das Suffix "Id" kann in beliebiger Groß- oder Kleinschreibung vorkommen.

Die folgenden Entitätstypen zeigen Beispiele für jede dieser Benennungskonventionen.

Post.TheBlogKey wird als Fremdschlüssel erkannt, da er dem Muster <navigation property name><principal key property name>entspricht:

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? TheBlogKey { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.TheBlogID wird als Fremdschlüssel erkannt, da er dem Muster <navigation property name>Identspricht:

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? TheBlogID { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.BlogKey wird als Fremdschlüssel erkannt, da er dem Muster <principal entity type name><principal key property name>entspricht:

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? BlogKey { get; set; }
    public Blog? TheBlog { get; set; }
}

Post.Blogid wird als Fremdschlüssel erkannt, da er dem Muster <principal entity type name>Identspricht:

public class Blog
{
    public int Key { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public int? Blogid { get; set; }
    public Blog? TheBlog { get; set; }
}

Hinweis

Bei 1:n-Navigationen müssen die Eigenschaften des Fremdschlüssels beim Typ mit der Referenznavigation liegen, da dieser die abhängige Entität darstellt. Bei 1:1-Beziehungen wird die Ermittlung einer Fremdschlüsseleigenschaft verwendet, um zu bestimmen, welcher Typ das abhängige Ende der Beziehung darstellt. Wenn keine Fremdschlüsseleigenschaft ermittelt wird, muss das abhängige Ende mit HasForeignKey konfiguriert werden. Beispiele hierfür finden Sie unter 1:1-Beziehungen .

Die oben genannten Regeln gelten auch für zusammengesetzte Fremdschlüssel, wobei jede Eigenschaft des Zusammengesetzten einen kompatiblen Typ mit der entsprechenden Eigenschaft des Primär- oder Alternativschlüssels aufweisen muss, und jeder Eigenschaftsname muss mit einer der oben beschriebenen Benennungskonventionen übereinstimmen.

Kardinalität bestimmen

EF verwendet die ermittelten Navigations- und Fremdschlüsseleigenschaften, um die Kardinalität der Beziehung gemeinsam mit den Haupt- und abhängigen Enden zu bestimmen.

  • Wenn eine nicht gepaarte Referenznavigation vorhanden ist, wird die Beziehung als unidirektionale Eins-zu-Viele-Beziehung mit der Referenznavigation am abhängigen Ende konfiguriert.
  • Wenn eine einzelne, ungepaarte Sammlungsnavigation vorhanden ist, wird die Beziehung als unidirektionale 1:n-Beziehung mit der Sammlungsnavigation am Ende des Prinzipals konfiguriert.
  • Wenn paarweise Verweise und Sammlungsnavigationen vorhanden sind, wird die Beziehung als bidirektionale 1:n-Beziehung konfiguriert, wobei die Sammlungsnavigation auf der Hauptseite erfolgt.
  • Wenn eine Referenznavigation mit einer anderen Referenznavigation gekoppelt ist, gehen Sie wie folgt vor:
    • Wenn eine Fremdschlüsseleigenschaft auf einer Seite entdeckt, aber nicht auf der anderen Seite, wurde, wird die Beziehung als bidirektionale 1:1-Beziehung mit der Fremdschlüsseleigenschaft am abhängigen Ende konfiguriert.
    • Andernfalls kann die abhängige Seite nicht bestimmt werden, und EF löst eine Ausnahme aus, die angibt, dass die abhängige Seite explizit konfiguriert werden muss.
  • Wenn eine Sammlungsnavigation mit einer anderen Sammlungsnavigation gekoppelt ist, wird die Beziehung als bidirektionale Viele-zu-Viele-Beziehung konfiguriert.

Verdeckte Fremdschlüsseleigenschaften

Wenn EF das abhängige Ende der Beziehung bestimmt hat, aber keine Fremdschlüsseleigenschaft ermittelt wurde, erstellt EF eine Schatteneigenschaft , um den Fremdschlüssel darzustellen. Die Schatteneigenschaft:

  • Weist den Typ der Primär- oder Alternative Schlüsseleigenschaft am Prinzipalende der Beziehung auf.
    • Der Typ ist standardmäßig nullfähig, sodass die Beziehung standardmäßig optional ist.
  • Wenn eine Navigation auf dem abhängigen Ende vorhanden ist, wird die Schatten-Fremdschlüsseleigenschaft mit diesem Navigationsnamen benannt, der mit dem Namen der Primär- oder Alternativen Schlüsseleigenschaft verkettet ist.
  • Wenn keine Navigationsstruktur am abhängigen Ende vorhanden ist, wird die Eigenschaft des Schatten-Fremdschlüssels mithilfe des Namens des Prinzipalentitätstyps benannt, der mit dem Namen der primären oder alternativen Schlüsseleigenschaft verkettet wird.

Kaskadierendes Delete

In der Regel sind notwendige Beziehungen so konfiguriert, dass sie den Löschvorgang weitergeben. Optionale Beziehungen sind so konfiguriert, dass das Löschen nicht weitergegeben wird.

m:n

M:n-Beziehungen weisen keine Prinzipal- und abhängigen Enden auf, und kein Ende enthält eine Fremdschlüsseleigenschaft. Stattdessen verwenden viele-zu-viele-Beziehungen eine Verknüpfungsentität, die Paare von Fremdschlüsseln enthält, die auf beide Enden der viele-zu-viele-Beziehung zeigen. Berücksichtigen Sie die folgenden Entitätstypen, für die eine Viele-zu-Viele-Beziehung nach Standard ermittelt wird.

public class Post
{
    public int Id { get; set; }
    public ICollection<Tag> Tags { get; } = new List<Tag>();
}

public class Tag
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>();
}

Die in dieser Ermittlung verwendeten Konventionen sind:

  • Der Verknüpfungsentitätstyp ist benannt <left entity type name><right entity type name>. Also, PostTag in diesem Beispiel.
    • Die Verknüpfungstabelle hat denselben Namen wie der Verknüpfungsentitätstyp.
  • Der Verknüpfungsentitätstyp erhält eine Fremdschlüsseleigenschaft für jede Richtung der Beziehung. Diese werden benannt <navigation name><principal key name>. Also sind in diesem Beispiel die Fremdschlüsseleigenschaften PostsId und TagsId.
    • Bei einer unidirektionalen Viele-zu-Viele-Beziehung wird die Fremdschlüsseleigenschaft ohne zugeordnete Navigation als <principal entity type name><principal key name> bezeichnet.
  • Die Fremdschlüsseleigenschaften sind nicht-nullfähig, daher sind beide Beziehungen zur Verknüpfungsentität erforderlich.
    • Die Konventionen für kaskadierende Löschung bedeuten, dass diese Beziehungen für die kaskadierende Löschung konfiguriert werden.
  • Der Verknüpfungsentitätstyp ist mit einem zusammengesetzten Primärschlüssel konfiguriert, der aus den beiden Fremdschlüsseleigenschaften besteht. In diesem Beispiel besteht der Primärschlüssel also aus PostsId und TagsId.

Dies führt zu dem folgenden EF-Modell:

Model:
  EntityType: Post
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Skip navigations:
      Tags (ICollection<Tag>) CollectionTag Inverse: Posts
    Keys:
      Id PK
  EntityType: Tag
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    Skip navigations:
      Posts (ICollection<Post>) CollectionPost Inverse: Tags
    Keys:
      Id PK
  EntityType: PostTag (Dictionary<string, object>) CLR Type: Dictionary<string, object>
    Properties:
      PostsId (no field, int) Indexer Required PK FK AfterSave:Throw
      TagsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
    Keys:
      PostsId, TagsId PK
    Foreign keys:
      PostTag (Dictionary<string, object>) {'PostsId'} -> Post {'Id'} Cascade
      PostTag (Dictionary<string, object>) {'TagsId'} -> Tag {'Id'} Cascade
    Indexes:
      TagsId

Das folgende Schema wird bei Verwendung von SQLite in die Datenbank übersetzt:

CREATE TABLE "Posts" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "Tag" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Tag" PRIMARY KEY AUTOINCREMENT);

CREATE TABLE "PostTag" (
    "PostsId" INTEGER NOT NULL,
    "TagsId" INTEGER NOT NULL,
    CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
    CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_PostTag_Tag_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tag" ("Id") ON DELETE CASCADE);

CREATE INDEX "IX_PostTag_TagsId" ON "PostTag" ("TagsId");

Indizes

Standardmäßig erstellt EF einen Datenbankindex für die Eigenschaft oder die Eigenschaften eines Fremdschlüssels. Der Typ des erstellten Indexes wird durch Folgendes bestimmt:

  • Die Kardinalität der Beziehung
  • Gibt an, ob die Beziehung optional oder erforderlich ist.
  • Die Anzahl der Eigenschaften, aus denen der Fremdschlüssel besteht

Für eine 1:n-Beziehung wird standardmäßig ein einfacher Index gemäß Konvention erstellt. Derselbe Index wird für optionale und erforderliche Beziehungen erstellt. Beispiel: auf SQLite:

CREATE INDEX "IX_Post_BlogId" ON "Post" ("BlogId");

Oder auf SQL Server:

CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);

Für eine erforderliche 1:1-Beziehung wird ein eindeutiger Index erstellt. Beispiel: auf SQLite:

CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");

Oder auf SQL Server:

CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]);

Bei optionalen 1:1-Beziehungen ist der in SQLite erstellte Index identisch:

CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");

Auf SQL Server wird jedoch ein IS NOT NULL Filter hinzugefügt, um Null-Fremdschlüsselwerte besser zu behandeln. Beispiel:

CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]) WHERE [BlogId] IS NOT NULL;

Bei zusammengesetzten Fremdschlüsseln wird ein Index erstellt, der alle Fremdschlüsselspalten abdeckt. Beispiel:

CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");

Hinweis

EF erstellt keine Indizes für Eigenschaften, die bereits von einer vorhandenen Index- oder Primärschlüsseleinschränkung abgedeckt sind.

So beenden Sie das Erstellen von EF-Indizes für Fremdschlüssel

Indizes haben Mehraufwand, und wie hier gefragt, ist es möglicherweise nicht immer geeignet, sie für alle FK-Spalten zu erstellen. Um dies zu erreichen, kann dies ForeignKeyIndexConvention beim Erstellen des Modells entfernt werden:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}

Bei Bedarf können Indizes für diese Fremdschlüsselspalten, die sie benötigen, weiterhin explizit erstellt werden.

Namen von Fremdschlüsseleinschränkungen

Fremdschlüsselbeschränkungen werden nach Konvention benannt FK_<dependent type name>_<principal type name>_<foreign key property name>. Bei zusammengesetzten Fremdschlüsseln wird <foreign key property name> zu einer Liste von Fremdschlüsseleigenschaftsnamen, die durch Unterstriche getrennt sind.

Weitere Ressourcen