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:
- Muss ein Verweistyp sein.
- Darf nicht explizit als primitiver Eigenschaftstyp konfiguriert worden sein.
- Darf von dem verwendeten Datenbankanbieter nicht als primitiver Eigenschaftstyp zugeordnet werden.
- Darf nicht automatisch in einen primitiven Eigenschaftstyp konvertiert werden, der von dem verwendeten Datenbankanbieter zugeordnet wird.
- 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
, daint
ein zugeordneter primitiver Typ ist.Blog.Title
, da „string“ ein zugeordneter primitiver Typ ist.Blog.Uri
, daUri
automatisch in einen zugeordneten primitiven Typ konvertiert wird.Blog.ConsoleKeyInfo
, daConsoleKeyInfo
ein C#-Werttyp ist.Blog.DefaultAuthor
, da die Eigenschaft keinen Setter besitzt.Author.Id
, daGuid
ein zugeordneter primitiver Typ ist.Author.Name
, da „string“ ein zugeordneter primitiver Typ ist.Author.BlogId
, daint
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>
, wobeiTEntity
ein Entitätstyp ist oder sein könnte. Dies bedeutet, dass für den Typ vonTEntity
Folgendes gilt:- Muss ein Verweistyp sein.
- Darf nicht explizit als primitiver Eigenschaftstyp konfiguriert worden sein.
- Darf von dem verwendeten Datenbankanbieter nicht als primitiver Eigenschaftstyp zugeordnet werden.
- Darf nicht automatisch in einen primitiven Eigenschaftstyp konvertiert werden, der von dem 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>();
}
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 alsoPostTag
.- 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 alsoPostsId
undTagsId
.- Bei einer unidirektionalen M:N-Eigenschaft erhält die Fremdschlüsseleigenschaft ohne zugeordnete Navigation den Namen
<principal entity type name><principal key name>
.
- Bei einer unidirektionalen M:N-Eigenschaft erhält die Fremdschlüsseleigenschaft ohne zugeordnete Navigation den Namen
- 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
undTagsId
.
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.