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 nieodnajdywane jako nawigacje referencyjne:

  • Blog.Id, ponieważ int jest mapowanym typem pierwotnym
  • Blog.Title, ponieważ ciąg jest mapowanym typem pierwotnym
  • Blog.Uri, ponieważ Uri jest automatycznie konwertowany na zamapowany typ pierwotny
  • Blog.ConsoleKeyInfo, ponieważ ConsoleKeyInfo jest typem wartości języka C#
  • Blog.DefaultAuthor, ponieważ właściwość nie ma elementu ustawianego
  • Author.Id, ponieważ Guid jest mapowanym typem pierwotnym
  • Author.Name, ponieważ ciąg jest mapowanym typem pierwotnym
  • Author.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 , gdzie TEntity jest lub może być typem jednostki. Oznacza to, że typ TEntity:
    • 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 to PostsId i TagsId.
    • 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ł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 i TagsId.

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