Teilen über


Konventionen für die Beziehungsermittlung

EF Core verwendet eine Reihe von Konventionen bei der Ermittlung und Erstellung eines Modells auf der Grundlage von Entitätstypklassen. In diesem Dokument werden die Konventionen zusammengefasst, die für die Ermittlung und Konfiguration von Beziehungen zwischen Entitätstypen verwendet werden.

Wichtig

Die hier beschriebenen Konventionen können durch explizite Konfiguration der Beziehung außer Kraft gesetzt werden, indem entweder Zuordnungsattribute oder die Modellerstellungs-API verwendet werden.

Tipp

Der folgende Code befindet sich in RelationshipConventions.cs.

Entdecken von Navigationen

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

Verweisnavigationen

Eine Eigenschaft eines Entitätstyps wird als Verweisnavigation ermittelt, wenn Folgendes gilt:

  • Die Eigenschaft ist öffentlich.
  • Die Eigenschaft verfügt über einen Getter und einen Setter.
    • Der Setter muss nicht öffentlich sein. Er kann privat sein oder über eine andere Erreichbarkeit verfügen.
    • Der Setter kann Init-only sein.
  • Der Eigenschaftstyp ist oder könnte ein Entitätstyp sein. Dies bedeutet, dass für den Typ Folgendes gilt:
  • Die Eigenschaft ist nicht statisch.
  • Die Eigenschaft ist keine Indexereigenschaft.

Berücksichtigen Sie beispielsweise folgende 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!;
}

Für diese Typen werden Blog.Author und Author.Blog als Verweisnavigationen ermittelt. Andererseits werden die folgenden Eigenschaften nicht als Verweisnavigationen ermittelt:

  • Blog.Id, da int ein zugeordneter primitiver Typ ist.
  • Blog.Title, da „string“ ein zugeordneter primitiver Typ ist.
  • Blog.Uri, da Uri automatisch in einen zugeordneten primitiven Typ konvertiert wird.
  • Blog.ConsoleKeyInfo, da ConsoleKeyInfo ein C#-Werttyp ist.
  • Blog.DefaultAuthor, da die Eigenschaft keinen Setter besitzt.
  • Author.Id, da Guid ein zugeordneter primitiver Typ ist.
  • Author.Name, da „string“ ein zugeordneter primitiver Typ ist.
  • Author.BlogId, da int ein zugeordneter primitiver Typ ist.

Sammlungsnavigationen

Eine Eigenschaft eines Entitätstyps wird als Sammlungsnavigation ermittelt, wenn Folgendes gilt:

  • Die Eigenschaft ist öffentlich.
  • Die Eigenschaft verfügt über einen Getter. Sammlungsnavigationen können Setter aufweisen, dies ist jedoch nicht erforderlich.
  • Der Eigenschaftentyp ist oder implementiert IEnumerable<TEntity>, wobei TEntity ein Entitätstyp ist oder sein könnte. Dies bedeutet, dass für den Typ von TEntity Folgendes gilt:
  • 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>();
}

Koppeln von Navigationen

Sobald eine Navigation erkannt wurde, die z. B. von Entität A zu Entität B führt, muss als nächstes festgestellt werden, ob diese Navigation über eine Umkehrung verfügt, die in die entgegengesetzte Richtung führt, d. h. von Entität B zu Entität A. Wenn eine solche Umkehrung gefunden wird, werden die beiden Navigationen zu einer einzelnen, bidirektionalen Beziehung gekoppelt.

Der Beziehungstyp wird dadurch bestimmt, ob es sich bei der Navigation und ihrer Umkehrung um Verweis- oder Sammlungsnavigationen handelt. Dies gilt insbesondere in folgenden Fällen:

  • Wenn eine Navigation eine Sammlungsnavigation und die andere eine Verweisnavigation ist, dann ist die Beziehung 1:N.
  • Wenn beide Navigationen Verweisnavigationen sind, dann ist die Beziehung 1:1.
  • Wenn beide Navigationen Sammlungsnavigationen sind, dann ist die Beziehung M:N.

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

Eine einzelne 1:N-Beziehung wird zwischen Blog und Post ermittelt, indem die Navigationen Blog.Posts und Post.Blog gekoppelt werden:

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 wird zwischen Blog und Author ermittelt, indem die Navigationen Blog.Author und Author.Blog gekoppelt werden:

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 M:N-Beziehung wird zwischen Post und Tag ermittelt, indem die Navigationen Post.Tags und Tag.Posts gekoppelt werden:

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 Navigationen 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 Beschreibungen hier beziehen sich auf die Beziehungen zwischen zwei verschiedenen Typen. Es ist jedoch möglich, dass ein und derselbe Typ an beiden Enden einer Beziehung steht und daher ein und derselbe Typ zwei Navigationen hat, die beide miteinander gekoppelt sind. Dies wird als „auf sich selbst verweisende Beziehung“ bezeichnet.

Ermitteln von Fremdschlüsseleigenschaften

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

  • Der Eigenschaftstyp ist mit dem Primär- oder Alternativschlüssel der Prinzipalentität kompatibel.
    • Die Typen sind kompatibel, wenn sie identisch sind oder wenn der Typ der Fremdschlüsseleigenschaft eine Nullwerte zulassende Version des Typs der Primär- oder Alternativschlüsseleigenschaft ist.
  • Der Eigenschaftsname entspricht einer der Namenskonventionen für eine Fremdschlüsseleigenschaft. Die Namenskonventionen 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 eine beliebige Groß-/Kleinschreibung aufweisen.

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

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>Id 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? 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>Id 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? Blogid { get; set; }
    public Blog? TheBlog { get; set; }
}

Hinweis

Im Falle von 1:N-Navigationen müssen die Fremdschlüsseleigenschaften beim Typ mit der Verweisnavigation liegen, da dies die abhängige Entität ist. 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 obigen Regeln gelten auch für zusammengesetzte Fremdschlüssel, wobei jede Eigenschaft des zusammengesetzten Schlüssels einen kompatiblen Typ mit der entsprechenden Eigenschaft des Primär- oder Alternativschlüssels aufweisen muss und jeder Eigenschaftsname einer der oben beschriebenen Namenskonventionen entsprechen muss.

Ermitteln der Kardinalität

EF verwendet die ermittelten Navigations- und Fremdschlüsseleigenschaften, um die Kardinalität der Beziehung zusammen mit ihren Prinzipal- und abhängigen Enden zu bestimmen:

  • Wenn es eine einzelne, ungekoppelte Verweisnavigation gibt, wird die Beziehung als unidirektionale 1:N-Beziehung konfiguriert, wobei sich die Verweisnavigation am abhängigen Ende befindet.
  • Wenn es eine einzelne, ungekoppelte Sammlungsnavigation gibt, wird die Beziehung als unidirektionale 1:N-Beziehung konfiguriert, wobei sich die Sammlungsnavigation am Prinzipalende befindet.
  • Wenn es gekoppelte Verweis- und Sammlungsnavigationen gibt, wird die Beziehung als bidirektionale 1:N-Beziehung konfiguriert, wobei sich die Sammlungsnavigation am Prinzipalende befindet.
  • Wenn eine Verweisnavigation mit einer anderen Verweisnavigation gekoppelt ist, dann:
    • Wenn eine Fremdschlüsseleigenschaft auf einer Seite erkannt wurde, aber nicht auf der anderen, dann wird die Beziehung als bidirektionale 1:1-Beziehung konfiguriert, wobei sich die Fremdschlüsseleigenschaft am abhängigen Ende befindet.
    • Andernfalls kann die abhängige Seite nicht ermittelt werden und EF löst eine Ausnahme aus, die besagt, dass die abhängige Seite explizit konfiguriert werden muss.
  • Wenn eine Sammlungsnavigation mit einer anderen Sammlungsnavigation gekoppelt ist, dann wird die Beziehung als bidirektionale M:N-Beziehung konfiguriert.

Schattenfremdschlüssel-Eigenschaften

Wenn EF das abhängige Ende der Beziehung ermittelt hat, aber keine Fremdschlüsseleigenschaft entdeckt wurde, erstellt EF eine Schatteneigenschaft, um den Fremdschlüssel darzustellen. Für die Schatteneigenschaft gilt Folgendes:

  • Sie weist den Typ der Primär- oder Alternativschlüsseleigenschaft am Prinzipalende der Beziehung auf.
    • Der Typ lässt standardmäßig Nullwerte zu, sodass die Beziehung standardmäßig optional ist.
  • Wenn eine Navigation am abhängigen Ende vorhanden ist, wird die Schattenfremdschlüssel-Eigenschaft mit diesem Navigationsnamen benannt, der mit dem Namen der Primär- oder Alternativschlüsseleigenschaft verkettet ist.
  • Wenn keine Navigation am abhängigen Ende vorhanden ist, wird die Schattenfremdschlüssel-Eigenschaft mit dem Namen des Prinzipalentitätstyps benannt, der mit dem Namen der Primär- oder Alternativschlüsseleigenschaft verkettet ist.

Kaskadierendes Delete

Gemäß Konvention sind die erforderlichen Beziehungen so konfiguriert, dass sie ein kaskadierendes Delete verwenden. Optionale Beziehungen sind so konfiguriert, dass kein kaskadierendes Delete erfolgt.

M:n

M:N-Beziehungen haben keine Prinzipal- und abhängigen Enden, und keines der beiden Enden enthält eine Fremdschlüsseleigenschaft. Stattdessen verwenden M:N-Beziehungen einen Joinentitätstyp, der Paare von Fremdschlüsseln enthält, die auf beide Enden der M:N-Beziehung zeigen. Betrachten Sie die folgenden Entitäten, für die gemäß Konvention eine M:N-Beziehung 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 bei dieser Ermittlung verwendeten Konventionen sind:

  • Der Joinentitätstyp erhält den Namen <left entity type name><right entity type name>. In diesem Beispiel also PostTag.
    • Die Jointabelle hat denselben Namen wie der Joinentitätstyp.
  • Der Joinentitätstyp erhält eine Fremdschlüsseleigenschaft für jede Richtung der Beziehung. Diese erhalten den Namen <navigation name><principal key name>. In diesem Beispiel sind die Fremdschlüsseleigenschaften also PostsId und TagsId.
    • Bei einer unidirektionalen M:N-Eigenschaft erhält die Fremdschlüsseleigenschaft ohne zugeordnete Navigation den Namen <principal entity type name><principal key name>.
  • Die Fremdschlüsseleigenschaften sind „Non-Nullable“, sodass beide Beziehungen zur Joinentität erforderlich sind.
    • Die Konventionen für das kaskadierende Delete bedeuten, dass diese Beziehungen für das kaskadierende Delete konfiguriert werden.
  • Der Joinentitä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

Dies wird bei Verwendung von SQLite in das folgende Datenbankschema ü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

Gemäß Konvention erstellt EF einen Datenbankindex für die Eigenschaft oder Eigenschaften eines Fremdschlüssels. Der erstellte Indextyp wird durch Folgendes bestimmt:

  • Kardinalität der Beziehung
  • Ob die Beziehung optional oder erforderlich ist
  • Anzahl der Eigenschaften, aus denen der Fremdschlüssel besteht

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

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

Oder unter SQL Server:

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

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

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

Oder unter 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");

Unter 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 Indizes für Fremdschlüssel durch EF

Indizes haben einen Mehraufwand und (wie hier gefragt) ist es vielleicht nicht immer sinnvoll, sie für alle FK-Spalten zu erstellen. Hierzu kann die ForeignKeyIndexConvention-Klasse beim Erstellen des Modells entfernt werden:

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

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

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.

Zusätzliche Ressourcen