Konwencje odnajdywania relacji
Program EF Core używa zestawu konwencji podczas odnajdywania i tworzenia modelu na podstawie klas typów jednostek. Ten dokument zawiera podsumowanie konwencji używanych do odnajdywania i konfigurowania relacji między typami jednostek.
Ważne
Konwencje opisane tutaj można zastąpić jawną konfiguracją relacji przy użyciu atrybutów mapowania lub interfejsu API tworzenia modelu.
Napiwek
Poniższy kod można znaleźć w pliku RelationshipConventions.cs.
Odnajdywanie nawigacji
Odnajdywanie relacji rozpoczyna się od odnajdywania nawigacji między typami jednostek.
Nawigacje referencyjne
Właściwość typu jednostki jest wykrywana jako nawigacja referencyjna w przypadku:
- Obiekt jest publiczny.
- Właściwość ma element getter i setter.
- Setter nie musi być publiczny; może być prywatny lub mieć inne ułatwienia dostępu.
- Elementem ustawianym może być tylko inicjowanie.
- Typ właściwości to lub może być typem jednostki. Oznacza to, że typ
- Musi być typem odwołania.
- Nie można jawnie skonfigurować jako typ właściwości pierwotnej.
- Nie może być mapowany jako typ właściwości pierwotnej używany przez dostawcę bazy danych.
- Nie może być automatycznie konwertowany na typ właściwości pierwotnej mapowany przez używanego dostawcę bazy danych.
- Właściwość nie jest statyczna.
- Właściwość nie jest właściwością indeksatora.
Rozważmy na przykład następujące typy jednostek:
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!;
}
Dla tych typów Blog.Author
i Author.Blog
są odnajdywane jako nawigacje referencyjne. Z drugiej strony następujące właściwości nie są odnajdywane jako nawigacje referencyjne:
Blog.Id
, ponieważint
jest mapowanym typem pierwotnymBlog.Title
, ponieważ ciąg jest mapowanym typem pierwotnymBlog.Uri
, ponieważUri
jest automatycznie konwertowany na zamapowany typ pierwotnyBlog.ConsoleKeyInfo
, ponieważConsoleKeyInfo
jest typem wartości języka C#Blog.DefaultAuthor
, ponieważ właściwość nie ma elementu ustawianegoAuthor.Id
, ponieważGuid
jest mapowanym typem pierwotnymAuthor.Name
, ponieważ ciąg jest mapowanym typem pierwotnymAuthor.BlogId
, ponieważint
jest mapowanym typem pierwotnym
Nawigacje po kolekcjach
Właściwość typu jednostki jest wykrywana jako nawigacja po kolekcji, gdy:
- Obiekt jest publiczny.
- Właściwość ma element getter. Nawigacje kolekcji mogą mieć zestawy, ale nie jest to wymagane.
- Typ właściwości to lub implementuje
IEnumerable<TEntity>
element , gdzieTEntity
jest lub może być typem jednostki. Oznacza to, że typTEntity
:- Musi być typem odwołania.
- Nie można jawnie skonfigurować jako typ właściwości pierwotnej.
- Nie może być mapowany jako typ właściwości pierwotnej używany przez dostawcę bazy danych.
- Nie może być automatycznie konwertowany na typ właściwości pierwotnej mapowany przez używanego dostawcę bazy danych.
- Właściwość nie jest statyczna.
- Właściwość nie jest właściwością indeksatora.
Na przykład w poniższym kodzie zarówno jak Blog.Tags
i Tag.Blogs
są odnajdywane jako nawigacje kolekcji:
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>();
}
Nawigacje parowania
Gdy na przykład zostanie odnaleziona nawigacja przechodząca od typu jednostki A do typu jednostki B, należy ją określić, czy ta nawigacja ma odwrotność kierunku odwrotnego — od typu jednostki B do typu jednostki A. Jeśli zostanie znaleziona taka odwrotność, obie nawigacje są połączone w celu utworzenia jednej, dwukierunkowej relacji.
Typ relacji zależy od tego, czy nawigacja i jej odwrotność to nawigacja referencyjna, czy nawigacja kolekcji. Szczególnie:
- Jeśli jedna nawigacja to nawigacja kolekcji, a druga to nawigacja referencyjna, relacja to jeden do wielu.
- Jeśli obie nawigacje są nawigacjami referencyjnymi, relacja to jeden do jednego.
- Jeśli obie nawigacje to nawigacje kolekcji, relacja to wiele do wielu.
Odnajdywanie każdego z tych typów relacji przedstawiono w poniższych przykładach:
Jedna relacja "jeden do wielu" jest wykrywana między elementami Blog
i Post
jest odnajdywane przez parowanie Blog.Posts
elementów i :Post.Blog
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; }
}
Jedna relacja "jeden do jednego" jest wykrywana między elementami Blog
i Author
jest wykrywana przez parowanie Blog.Author
elementów i :Author.Blog
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; }
}
Jedna relacja wiele-do-wielu jest odnajdywane między elementami Post
i Tag
jest odnajdywane przez parowanie Post.Tags
elementów i :Tag.Posts
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>();
}
Uwaga
To parowanie nawigacji może być nieprawidłowe, jeśli obie nawigacje reprezentują dwie, różne, jednokierunkowe relacje. W takim przypadku należy jawnie skonfigurować dwie relacje.
Parowanie relacji działa tylko wtedy, gdy istnieje jedna relacja między dwoma typami. Należy jawnie skonfigurować wiele relacji między dwoma typami.
Uwaga
Opisy w tym miejscu dotyczą relacji między dwoma różnymi typami. Istnieje jednak możliwość, że ten sam typ będzie znajdować się na obu końcach relacji, a w związku z tym jeden typ ma dwie nawigacje sparowane ze sobą. Jest to nazywane relacją odwołującą się do siebie.
Odnajdywanie właściwości klucza obcego
Po odnalezieniu lub jawnym skonfigurowaniu nawigacji dla relacji te nawigacje są używane do odnajdywania odpowiednich właściwości klucza obcego dla relacji. Właściwość jest odnajdywane jako klucz obcy, gdy:
- Typ właściwości jest zgodny z kluczem podstawowym lub alternatywnym w typie jednostki głównej.
- Typy są zgodne, jeśli są takie same lub jeśli typ właściwości klucza obcego jest wersją dopuszczaną do wartości null typu właściwości podstawowego lub alternatywnego klucza.
- Nazwa właściwości jest zgodna z jedną z konwencji nazewnictwa dla właściwości klucza obcego. Konwencje nazewnictwa to:
<navigation property name><principal key property name>
<navigation property name>Id
<principal entity type name><principal key property name>
<principal entity type name>Id
- Ponadto jeśli zależny koniec został jawnie skonfigurowany przy użyciu interfejsu API tworzenia modelu, a zależny klucz podstawowy jest zgodny, klucz podstawowy zależny będzie również używany jako klucz obcy.
Napiwek
Sufiks "Id" może mieć dowolną wielkość liter.
W poniższych typach jednostek przedstawiono przykłady dla każdej z tych konwencji nazewnictwa.
Post.TheBlogKey
jest wykrywany jako klucz obcy, ponieważ pasuje do wzorca <navigation property name><principal key property name>
:
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
jest wykrywany jako klucz obcy, ponieważ pasuje do wzorca <navigation property name>Id
:
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
jest wykrywany jako klucz obcy, ponieważ pasuje do wzorca <principal entity type name><principal key property name>
:
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
jest wykrywany jako klucz obcy, ponieważ pasuje do wzorca <principal entity type name>Id
:
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; }
}
Uwaga
W przypadku nawigacji "jeden do wielu" właściwości klucza obcego muszą znajdować się w typie z nawigacją referencyjną, ponieważ będzie to jednostka zależna. W przypadku relacji jeden do jednego odnajdywanie właściwości klucza obcego służy do określenia, który typ reprezentuje zależny koniec relacji. Jeśli nie zostanie odnaleziona żadna właściwość klucza obcego, należy skonfigurować koniec zależny przy użyciu polecenia HasForeignKey
. Aby zapoznać się z przykładami, zobacz Relacje jeden do jednego.
Powyższe reguły dotyczą również złożonych kluczy obcych, gdzie każda właściwość złożonego musi mieć zgodny typ z odpowiednią właściwością klucza podstawowego lub alternatywnego, a każda nazwa właściwości musi być zgodna z jedną z konwencji nazewnictwa opisanych powyżej.
Określanie kardynalności
Program EF używa odnalezionych nawigacji i właściwości klucza obcego, aby określić kardynalność relacji wraz z jej głównymi i zależnymi końcami:
- Jeśli istnieje jeden, nieparzysty nawigacja referencyjna, relacja jest skonfigurowana jako jednokierunkowy jeden do wielu, z nawigacją referencyjną na końcu zależnym.
- Jeśli istnieje jedna, nieparzysty nawigacja kolekcji, relacja jest skonfigurowana jako jednokierunkowy jeden do wielu, a nawigacja kolekcji na końcu podmiotu zabezpieczeń.
- Jeśli istnieją sparowane odwołania i nawigacje kolekcji, relacja jest skonfigurowana jako dwukierunkowy jeden do wielu z nawigacją kolekcji na końcu podmiotu zabezpieczeń.
- Jeśli nawigacja referencyjna jest sparowana z inną nawigacją referencyjną, wówczas:
- Jeśli właściwość klucza obcego została odnaleziona po jednej stronie, ale nie po drugiej, relacja jest skonfigurowana jako dwukierunkowa właściwość jeden do jednego, z właściwością klucza obcego na końcu zależnym.
- W przeciwnym razie nie można określić strony zależnej i program EF zgłasza wyjątek wskazujący, że zależność musi być jawnie skonfigurowana.
- Jeśli nawigacja kolekcji jest sparowana z inną nawigacją kolekcji, relacja jest skonfigurowana jako dwukierunkowa relacja wiele-do-wielu.
Właściwości klucza obcego w tle
Jeśli ef ustalił zależny koniec relacji, ale nie odnaleziono właściwości klucza obcego, program EF utworzy właściwość cienia reprezentującą klucz obcy. Właściwość cienia:
- Ma typ właściwości klucza podstawowego lub alternatywnego na końcu głównej relacji.
- Typ jest domyślnie dopuszczany do wartości null, co sprawia, że relacja jest domyślnie opcjonalna.
- Jeśli na końcu zależnym znajduje się nawigacja, właściwość klucza obcego w tle nosi nazwę przy użyciu tej nazwy nawigacji łączonej z nazwą właściwości klucza podstawowego lub alternatywnego.
- Jeśli na końcu zależnym nie ma nawigacji, właściwość klucza obcego w tle nosi nazwę przy użyciu nazwy typu jednostki głównej, która jest połączona z nazwą właściwości klucza podstawowego lub alternatywnego.
Usuwanie kaskadowe
Zgodnie z konwencją wymagane relacje są skonfigurowane do usuwania kaskadowego. Opcjonalne relacje są skonfigurowane tak, aby nie usuwać kaskadowo.
Wiele do wielu
Relacje wiele-do-wielu nie mają końców głównych i zależnych, a żaden koniec nie zawiera właściwości klucza obcego. Zamiast tego relacje wiele-do-wielu używają typu jednostki sprzężenia, który zawiera pary kluczy obcych wskazujących jeden koniec wiele do wielu. Rozważ następujące typy jednostek, dla których relacja wiele-do-wielu jest wykrywana zgodnie z konwencją:
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>();
}
Konwencje używane w tym odnajdywaniem to:
- Typ jednostki sprzężenia ma nazwę
<left entity type name><right entity type name>
.PostTag
W tym przykładzie.- Tabela sprzężenia ma taką samą nazwę jak typ jednostki sprzężenia.
- Typ jednostki sprzężenia ma właściwość klucza obcego dla każdego kierunku relacji. Są one nazwane .
<navigation name><principal key name>
W tym przykładzie właściwości klucza obcego toPostsId
iTagsId
.- W przypadku jednokierunkowej właściwości wiele-do-wielu właściwość klucza obcego bez skojarzonej nawigacji nosi nazwę
<principal entity type name><principal key name>
.
- W przypadku jednokierunkowej właściwości wiele-do-wielu właściwość klucza obcego bez skojarzonej nawigacji nosi nazwę
- Właściwości klucza obcego są niepuste, co sprawia, że obie relacje z jednostką sprzężenia są wymagane.
- Konwencje usuwania kaskadowego oznaczają, że te relacje zostaną skonfigurowane do usuwania kaskadowego.
- Typ jednostki sprzężenia jest skonfigurowany przy użyciu złożonego klucza podstawowego składającego się z dwóch właściwości klucza obcego. W tym przykładzie klucz podstawowy składa się z
PostsId
elementów iTagsId
.
Spowoduje to wykonanie następującego modelu EF:
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
I tłumaczy się na następujący schemat bazy danych podczas korzystania z sqlite:
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");
Indeksy
Zgodnie z konwencją program EF tworzy indeks bazy danych dla właściwości lub właściwości klucza obcego. Typ utworzonego indeksu jest określany przez:
- Kardynalność relacji
- Czy relacja jest opcjonalna, czy wymagana
- Liczba właściwości tworzących klucz obcy
W przypadku relacji jeden do wielu prosty indeks jest tworzony zgodnie z konwencją. Ten sam indeks jest tworzony dla relacji opcjonalnych i wymaganych. Na przykład w witrynie SQLite:
CREATE INDEX "IX_Post_BlogId" ON "Post" ("BlogId");
Lub w programie SQL Server:
CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);
W przypadku wymaganej relacji jeden do jednego tworzony jest unikatowy indeks. Na przykład w witrynie SQLite:
CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");
Lub na serwerze SQL:
CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]);
W przypadku opcjonalnych relacji jeden do jednego indeks utworzony w sqlite jest taki sam:
CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");
Jednak w programie SQL Server dodawany jest filtr, IS NOT NULL
aby lepiej obsługiwać wartości klucza obcego o wartościach null. Przykład:
CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]) WHERE [BlogId] IS NOT NULL;
W przypadku złożonych kluczy obcych tworzony jest indeks obejmujący wszystkie kolumny klucza obcego. Na przykład:
CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");
Uwaga
Program EF nie tworzy indeksów dla właściwości, które są już objęte istniejącym ograniczeniem indeksu lub klucza podstawowego.
Jak zatrzymać tworzenie indeksów dla kluczy obcych przez program EF
Indeksy mają obciążenie i, jak zapytano tutaj, może nie zawsze być odpowiednie, aby utworzyć je dla wszystkich kolumn FK. Aby to osiągnąć, ForeignKeyIndexConvention
można go usunąć podczas kompilowania modelu:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}
W razie potrzeby indeksy można nadal jawnie tworzyć dla tych kolumn kluczy obcych, które ich potrzebują.
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.
Dodatkowe zasoby
- Wideo dotyczące standupu społeczności danych platformy .NET na temat konwencji modelu niestandardowego.