Konvence pro zjišťování relací

EF Core používá sadu konvencí při zjišťování a vytváření modelu na základě tříd typů entit. Tento dokument shrnuje konvence používané ke zjišťování a konfiguraci vztahů mezi typy entit.

Důležité

Zde popsané konvence je možné přepsat explicitní konfigurací relace pomocí atributů mapování nebo rozhraní API pro sestavení modelu.

Tip

Níže uvedený kód najdete v souboru RelationshipConventions.cs.

Zjišťování navigačních panelů

Zjišťování relací začíná zjišťováním navigace mezi typy entit.

Referenční navigace

Vlastnost typu entity je zjištěna jako referenční navigace v těchto případech:

  • Vlastnost je veřejná.
  • Vlastnost má getter a setter.
    • Setter nemusí být veřejný; může být soukromý nebo může mít jakoukoli jinou přístupnost.
    • Setter může být jen inicializační.
  • Typ vlastnosti je nebo může být typem entity. To znamená, že typ
    • Musí to být odkazový typ.
    • Nesmí být nakonfigurován explicitně jako primitivní typ vlastnosti.
    • Nesmí být mapován jako primitivní typ vlastnosti používaného poskytovatelem databáze.
    • Nesmí se automaticky převést na primitivní typ vlastnosti namapovaný poskytovatelem databáze, který používá.
  • Vlastnost není statická.
  • Vlastnost není vlastnost indexeru.

Představte si například následující typy entit:

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!;
}

Pro tyto typy Blog.Author a Author.Blog jsou zjištěny jako referenční navigace. Na druhou stranu se jako referenční navigace nezjistí následující vlastnosti:

  • Blog.Id, protože int je mapovaný primitivní typ
  • Blog.Title, protože 'string' je mapovaný primitivní typ
  • Blog.Uri, protože Uri je automaticky převeden na mapovaný primitivní typ
  • Blog.ConsoleKeyInfo, protože ConsoleKeyInfo je typ hodnoty jazyka C#.
  • Blog.DefaultAuthor, protože vlastnost nemá setter
  • Author.Id, protože Guid je mapovaný primitivní typ
  • Author.Name, protože 'string' je mapovaný primitivní typ
  • Author.BlogId, protože int je mapovaný primitivní typ

Navigace v kolekcích

Vlastnost typu entity se zjistí jako navigace v kolekci v těchto případech:

  • Vlastnost je veřejná.
  • Vlastnost má getter. Navigace v kolekcích můžou mít settery, ale není to nutné.
  • Typ vlastnosti je nebo implementuje IEnumerable<TEntity>, kde TEntity je nebo může být typ entity. To znamená, že typ TEntity:
    • Musí to být odkazový typ.
    • Nesmí být nakonfigurován explicitně jako primitivní typ vlastnosti.
    • Nesmí být mapován jako primitivní typ vlastnosti používaného poskytovatelem databáze.
    • Nesmí se automaticky převést na primitivní typ vlastnosti namapovaný poskytovatelem databáze, který používá.
  • Vlastnost není statická.
  • Vlastnost není vlastnost indexeru.

Například v následujícím kódu jsou zjištěny obě Blog.Tags a Tag.Blogs jsou zjištěny jako navigace kolekce:

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>();
}

Párování navigačních panelů

Jakmile se zjistí navigace, například typ entity A na typ entity B, musí být dále určena, pokud má tato navigace inverzní funkci, která se nachází v opačném směru– to znamená, že od typu entity B k typu entity A. Pokud je taková inverzní funkce nalezena, obě navigace jsou spárovány dohromady a tvoří jednu obousměrnou relaci.

Typ relace je určen, zda navigace a její inverzní jsou odkazy nebo navigace kolekce. Konkrétně:

  • Pokud je jedna navigace v kolekci navigace a druhá je referenční navigace, je relace 1:N.
  • Pokud jsou obě navigace referenčními navigacemi, je relace 1:1.
  • Pokud jsou obě navigace navigace kolekcí, je relace M:N.

Zjišťování každého z těchto typů relací je znázorněno v následujících příkladech:

Je zjištěna jedna relace 1:N a Post je zjištěna Blog spárováním Blog.Posts a Post.Blog navigacemi:

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; }
}

Je zjištěna jedna relace 1:1 a BlogAuthor je zjištěna spárováním Blog.Author a Author.Blog navigacemi:

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; }
}

Je zjištěna jedna relace M:N a Tag je zjištěna Post spárováním Post.Tags a Tag.Posts navigacemi:

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>();
}

Poznámka

Toto párování navigace může být nesprávné, pokud dvě navigace představují dvě, různé, jednosměrné relace. V tomto případě musí být tyto dvě relace nakonfigurované explicitně.

Párování relací funguje jenom v případech, kdy existuje jeden vztah mezi dvěma typy. Explicitně musí být nakonfigurované více relací mezi dvěma typy.

Poznámka

Popisy jsou z hlediska vztahů mezi dvěma různými typy. Je však možné, aby byl stejný typ na obou koncích relace, a proto aby jeden typ měl dvě navigace spárované s ostatními. Tomu se říká vztah odkazující na sebe.

Zjišťování vlastností cizího klíče

Po zjištění nebo explicitní konfiguraci navigace pro relaci se tyto navigace použijí ke zjištění odpovídajících vlastností cizího klíče pro relaci. Vlastnost se zjistí jako cizí klíč v případech, kdy:

  • Typ vlastnosti je kompatibilní s primárním nebo alternativním klíčem u typu hlavní entity.
    • Typy jsou kompatibilní, pokud jsou stejné nebo pokud je typ vlastnosti cizího klíče verze vlastnosti primárního nebo alternativního klíče s možnou hodnotou null.
  • Název vlastnosti odpovídá jedné z konvencí pojmenování vlastnosti cizího klíče. Zásady vytváření názvů jsou:
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity type name><principal key property name>
    • <principal entity type name>Id
  • Kromě toho platí, že pokud je závislý konec explicitně nakonfigurovaný pomocí rozhraní API pro vytváření modelů a závislý primární klíč je kompatibilní, použije se jako cizí klíč také závislý primární klíč.

Tip

Přípona ID může mít libovolnou velikost znaků.

Následující typy entit zobrazují příklady pro každou z těchto konvencí pojmenování.

Post.TheBlogKey je zjištěn jako cizí klíč, protože odpovídá vzoru <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 je zjištěn jako cizí klíč, protože odpovídá vzoru <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 je zjištěn jako cizí klíč, protože odpovídá vzoru <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 je zjištěn jako cizí klíč, protože odpovídá vzoru <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; }
}

Poznámka

V případě navigace 1:N musí být vlastnosti cizího klíče na typu s odkazovou navigaci, protože to bude závislá entita. V případě relací 1:1 se zjišťování vlastnosti cizího klíče používá k určení typu, který představuje závislý konec relace. Pokud není zjištěna žádná vlastnost cizího klíče, musí být závislý konec nakonfigurován pomocí HasForeignKey. Příklady těchto relací najdete v tématu Relace 1:1.

Výše uvedená pravidla platí také pro složené cizí klíče, kde každá vlastnost složeného souboru musí mít kompatibilní typ s odpovídající vlastností primárního nebo alternativního klíče a každý název vlastnosti musí odpovídat jedné z výše popsaných zásad vytváření názvů.

Určení kardinality

Ef používá zjištěné navigace a vlastnosti cizího klíče k určení kardinality relace společně s jeho hlavními a závislými konci:

  • Pokud existuje jedna, nezaplacená referenční navigace, relace se nakonfiguruje jako jednosměrný 1 :N s odkazovou navigaci na závislém konci.
  • Pokud existuje jedna, nezaplacená navigace v kolekci, relace se nakonfiguruje jako jednosměrný 1 :N s navigačním panelem kolekce na konci objektu zabezpečení.
  • Pokud jsou spárované odkazy a navigace kolekce, relace se nakonfiguruje jako obousměrný 1:N s navigačním panelem kolekce na konci objektu zabezpečení.
  • Pokud je navigace s odkazem spárovaná s jinou odkazovou navigaci, postupujte takto:
    • Pokud byla vlastnost cizího klíče zjištěna na jedné straně, ale ne na druhé, je relace nakonfigurována jako obousměrný 1:1 s vlastností cizího klíče na závislém konci.
    • V opačném případě nelze určit závislé straně a EF vyvolá výjimku, která indikuje, že závislý musí být explicitně nakonfigurován.
  • Pokud je navigace v kolekci spárovaná s jinou navigaci v kolekci, je relace nakonfigurována jako obousměrný M:N.

Vlastnosti stínového cizího klíče

Pokud ef určil závislý konec relace, ale nebyla zjištěna žádná vlastnost cizího klíče, ef vytvoří stínovou vlastnost představující cizí klíč. Stínová vlastnost:

  • Má typ vlastnosti primárního nebo alternativního klíče na konci objektu zabezpečení relace.
    • Typ je ve výchozím nastavení nullable, takže relace je ve výchozím nastavení volitelná.
  • Pokud je na závislém konci navigace, je vlastnost stínového cizího klíče pojmenována pomocí tohoto názvu navigace zřetězeným s názvem vlastnosti primárního nebo alternativního klíče.
  • Pokud na závislém konci neexistuje žádná navigace, je vlastnost stínového cizího klíče pojmenována pomocí názvu typu hlavní entity zřetězený s názvem vlastnosti primárního nebo alternativního klíče.

Kaskádové odstranění

Podle konvence jsou požadované relace nakonfigurované tak, aby kaskádové odstranění. Volitelné relace jsou nakonfigurované tak, aby se kaskádové odstranění neodstraňovat.

N:N

Relace M:N nemají hlavní a závislé konce a ani konec neobsahuje vlastnost cizího klíče. Relace M:N místo toho používají typ entity spojení, který obsahuje páry cizích klíčů odkazující na jeden z koncích M:N. Vezměte v úvahu následující typy entit, pro které je konvence zjištěn vztah M:N:

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>();
}

Konvence používané v tomto zjišťování jsou:

  • Typ entity join má název <left entity type name><right entity type name>. PostTag Takže v tomto příkladu.
    • Tabulka spojení má stejný název jako typ entity spojení.
  • Typ entity join má vlastnost cizího klíče pro každý směr relace. Tyto názvy jsou pojmenovány <navigation name><principal key name>. V tomto příkladu jsou PostsId tedy vlastnosti cizího klíče a TagsId.
    • Pro jednosměrný M:N má vlastnost cizího klíče bez přidružené navigace název <principal entity type name><principal key name>.
  • Vlastnosti cizího klíče nejsou nullable, což z obou relací s entitou spojení vyžaduje.
    • Konvence kaskádového odstranění znamenají, že tyto relace budou nakonfigurovány pro kaskádové odstranění.
  • Typ entity join se konfiguruje se složeným primárním klíčem, který se skládá ze dvou vlastností cizího klíče. V tomto příkladu je tedy primární klíč tvořen PostsId a TagsId.

Výsledkem je následující model 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

A při použití SQLite se přeloží na následující schéma databáze:

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

Indexy

Ef vytvoří index databáze pro vlastnost nebo vlastnosti cizího klíče. Typ vytvořeného indexu určuje:

  • Kardinalita relace
  • Bez ohledu na to, jestli je relace volitelná nebo povinná
  • Počet vlastností, které tvoří cizí klíč

Pro relaci 1:N se konvence vytvoří jednoduchý index. Stejný index se vytvoří pro volitelné a požadované relace. Například na SQLite:

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

Nebo na SQL Serveru:

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

Pro požadovanou relaci 1:1 se vytvoří jedinečný index. Například na SQLite:

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

Nebo na SQL Serveru:

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

U volitelných relací 1:1 je index vytvořený na SQLite stejný:

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

Na SQL Serveru se ale přidá filtr pro IS NOT NULL lepší zpracování hodnot cizího klíče null. Příklad:

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

Pro složené cizí klíče se vytvoří index pokrývající všechny sloupce cizího klíče. Příklad:

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

Poznámka

EF nevytvoří indexy pro vlastnosti, které jsou již pokryty existujícím omezením indexu nebo primárního klíče.

Jak zastavit vytváření indexů EF pro cizí klíče

Indexy mají režijní náklady a jak je zde uvedeno, nemusí být vždy vhodné je vytvořit pro všechny sloupce FK. Abyste toho dosáhli, ForeignKeyIndexConvention můžete ho při sestavování modelu odebrat:

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

V případě potřeby je možné indexy i nadále explicitně vytvářet pro sloupce cizího klíče, které je potřebují.

Názvy omezení cizího klíče

Omezení cizího klíče podle konvence jsou pojmenována FK_<dependent type name>_<principal type name>_<foreign key property name>. U složených cizích klíčů <foreign key property name> se stane podtržítkem oddělený seznam názvů vlastností cizího klíče.

Další prostředky