Delen via


Conventies voor relatiedetectie

EF Core maakt gebruik van een set conventies bij het detecteren en bouwen van een model op basis van entiteitstypeklassen. Dit document bevat een overzicht van de conventies die worden gebruikt voor het detecteren en configureren van relaties tussen entiteitstypen.

Belangrijk

De hier beschreven conventies kunnen worden overschreven door expliciete configuratie van de relatie met behulp van toewijzingskenmerken of de API voor het bouwen van modellen.

Aanbeveling

De onderstaande code vindt u in RelationshipConventions.cs.

Navigatie ontdekken

Relatiedetectie begint met het detecteren van navigatie tussen entiteitstypen .

Referentienavigatie

Een eigenschap van een entiteitstype wordt als referentienavigatie gedetecteerd wanneer:

  • De accommodatie is openbaar.
  • De eigenschap heeft een getter en een setter.
  • Het eigenschapstype is of kan een entiteitstype zijn. Dit betekent dat het type
  • De eigenschap is niet statisch.
  • De eigenschap is geen indexeerfunctieeigenschap.

Denk bijvoorbeeld aan de volgende entiteitstypen:

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

Voor deze typen worden Blog.Author en Author.Blog gedetecteerd als referentienavigaties. Aan de andere kant worden de volgende eigenschappen niet gedetecteerd als referentienavigatie:

  • Blog.Id, omdat int een toegewezen primitief type is
  • Blog.Title, omdat 'string' een toegewezen primitief type is
  • Blog.Uri, omdat Uri automatisch wordt geconverteerd naar een toegewezen primitief type
  • Blog.ConsoleKeyInfo, omdat ConsoleKeyInfo het een C#-waardetype is
  • Blog.DefaultAuthor, omdat de eigenschap geen setter heeft
  • Author.Id, omdat Guid een toegewezen primitief type is
  • Author.Name, omdat 'string' een toegewezen primitief type is
  • Author.BlogId, omdat int een toegewezen primitief type is

Verzamelingsnavigatie

Een eigenschap van een entiteitstype wordt gedetecteerd als een verzamelingsnavigatie wanneer:

  • De accommodatie is openbaar.
  • De accommodatie heeft een getter. Verzamelingsnavigatie kan setters hebben, maar dit is niet vereist.
  • Het eigenschapstype is IEnumerable<TEntity>, of implementeert IEnumerable<TEntity>, waarbij een entiteitstype is of kan zijn. Dit betekent dat het typeTEntity:
  • De eigenschap is niet statisch.
  • De eigenschap is geen indexeerfunctieeigenschap.

Bijvoorbeeld worden zowel Blog.Tags als Tag.Blogs in de volgende code herkend als verzamelingsnavigaties.

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

Koppelen van navigaties

Zodra een navigatie van bijvoorbeeld entiteitstype A naar entiteitstype B wordt gedetecteerd, moet het vervolgens worden bepaald of deze navigatie een omgekeerde richting heeft, dat wil zeggen, van entiteitstype B tot entiteitstype A. Als een dergelijke inverse wordt gevonden, worden de twee navigaties gekoppeld om één tweerichtingsrelatie te vormen.

Het type relatie wordt bepaald door of de navigatie en de inverse ervan verwijzen of verzamelingsnavigatie zijn. Specifiek:

  • Als de ene navigatie een verzamelingsnavigatie is en de andere een referentienavigatie is, is de relatie een-op-veel.
  • Als beide navigaties verwijzingsnavigatie zijn, is de relatie een-op-een.
  • Als beide navigaties verzamelingsnavigatie zijn, is de relatie veel-op-veel.

De detectie van elk van deze typen relaties wordt weergegeven in de onderstaande voorbeelden:

Een enkele een-op-veel-relatie tussen Blog en Post wordt gedetecteerd door de Blog.Posts en Post.Blog navigatie te koppelen:

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

Er wordt een een-op-een-relatie ontdekt tussen Blog en Author, vastgesteld door de Blog.Author en Author.Blog navigatie te koppelen.

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

Er wordt een veel-op-veel-relatie gedetecteerd tussen Post en Tag door de Post.Tags en Tag.Posts navigaties te koppelen.

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

Opmerking

Deze koppeling van navigaties kan onjuist zijn als de twee navigaties twee, verschillende, unidirectionele relaties vertegenwoordigen. In dit geval moeten de twee relaties expliciet worden geconfigureerd.

Het koppelen van relaties werkt alleen wanneer er één relatie tussen twee typen bestaat. Meerdere relaties tussen twee typen moeten expliciet worden geconfigureerd.

Opmerking

De beschrijvingen hier zijn in termen van relaties tussen twee verschillende typen. Het is echter mogelijk dat hetzelfde type zich aan beide uiteinden van een relatie bevindt, waardoor een enkel type twee navigaties kan hebben die aan elkaar zijn gekoppeld. Dit wordt een zelfverwijzende relatie genoemd.

Eigenschappen van vreemde-sleutels ontdekken

Zodra de navigaties voor een relatie zijn ontdekt of expliciet geconfigureerd, worden deze navigaties gebruikt om de juiste eigenschappen van refererende sleutels voor de relatie te ontdekken. Een eigenschap wordt als een vreemde sleutel gedetecteerd wanneer:

  • Het eigenschapstype is compatibel met de primaire of alternatieve sleutel van het type principal-entiteit.
    • Typen zijn compatibel als ze hetzelfde zijn of als het eigenschapstype refererende sleutel een null-versie is van het primaire of alternatieve sleuteleigenschapstype.
  • De naam van de eigenschap komt overeen met een van de naamgevingsconventies voor een foreign key-eigenschap. De naamconventies zijn:
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity type name><principal key property name>
    • <principal entity type name>Id
  • In aanvulling op, als het afhankelijke einde expliciet is geconfigureerd met behulp van de Model Building API en de afhankelijke primaire sleutel compatibel is, dan wordt de afhankelijke primaire sleutel ook gebruikt als de buitenlandse sleutel.

Aanbeveling

Het achtervoegsel Id kan elke behuizing hebben.

De volgende entiteitstypen bevatten voorbeelden voor elk van deze naamconventies.

Post.TheBlogKey wordt als de foreign key ontdekt omdat het patroon overeenkomt met <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 wordt als de foreign key ontdekt omdat het patroon overeenkomt met <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 wordt als de foreign key ontdekt omdat het patroon overeenkomt met <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 wordt als de foreign key ontdekt omdat het patroon overeenkomt met <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; }
}

Opmerking

In het geval van een-op-veel-navigaties moeten de eigenschappen van de vreemde sleutel zich op het type met de verwijzingsnavigatie bevinden, omdat dit de afhankelijke entiteit is. In het geval van een-op-een-relaties wordt de ontdekking van een vreemde sleuteleigenschap gebruikt om te bepalen welk type de afhankelijke kant van de relatie vertegenwoordigt. Als er geen refererende sleuteleigenschap wordt gedetecteerd, moet het afhankelijke einde worden geconfigureerd met behulp van HasForeignKey. Zie een-op-een-relaties voor voorbeelden hiervan.

De bovenstaande regels zijn ook van toepassing op samengestelde buitenlandse sleutels, waarbij elk onderdeel van de samengestelde sleutel een compatibel type moet hebben met de overeenkomstige eigenschap van de primaire of alternatieve sleutel, en elke naam moet overeenkomen met een van de hierboven beschreven naamconventies.

Kardinaliteit bepalen

EF gebruikt de gedetecteerde navigatie- en refererende sleuteleigenschappen om de kardinaliteit van de relatie samen met de belangrijkste en afhankelijke einden te bepalen:

  • Als er één, ongepaarde verwijzingsnavigatie is, wordt de relatie geconfigureerd als een unidirectionale een-op-veel met de verwijzingsnavigatie aan de afhankelijke kant.
  • Als er een ongepaarde verzamelingsnavigatie is, wordt de relatie geconfigureerd als een een-op-veel, met de verzamelingsnavigatie aan het hoofdeinde.
  • Als er gekoppelde referentie- en verzamelingsnavigatie zijn, wordt de relatie geconfigureerd als een bidirectionele een-op-veel- met de verzamelingsnavigatie aan het principal-einde.
  • Als een verwijzingsnavigatie is gekoppeld aan een andere referentienavigatie, gaat u als volgt te werk:
    • Als aan de ene kant een foreign key-eigenschap is gedetecteerd, maar niet aan de andere kant, wordt de relatie geconfigureerd als een bidirectionele een-op-een, met de foreign key-eigenschap aan het afhankelijke einde.
    • Anders kan de afhankelijke zijde niet worden bepaald en genereert EF een uitzondering die aangeeft dat de afhankelijke expliciet moet worden geconfigureerd.
  • Als een verzamelingsnavigatie is gekoppeld aan een andere verzamelingsnavigatie, wordt de relatie geconfigureerd als een bidirectionele veel-op-veel-relatie.

Eigenschappen van schaduw-vreemde-sleutel

Als EF het afhankelijke einde van de relatie heeft bepaald, maar er geen refererende sleuteleigenschap is gedetecteerd, maakt EF een schaduweigenschap om de refererende sleutel weer te geven. De schaduweigenschap:

  • Heeft het type van de primaire of alternatieve sleutel aan het hoofdeinde van de relatie.
    • Het type wordt standaard null-baar gemaakt, waardoor de relatie standaard optioneel is.
  • Als er een navigatie aan het afhankelijke einde is, wordt de schaduweigenschap van de vreemde sleutel genoemd met behulp van deze navigatienaam, samengevoegd met de naam van de primaire of alternatieve sleuteleigenschap.
  • Als er geen navigatie is aan het afhankelijke einde, wordt de schaduw buitenlandse sleuteleigenschap benoemd door de naam van het hoofd-entiteitstype te combineren met de naam van de primaire of alternatieve sleuteleigenschap.

Cascadeverwijdering

Standaard worden vereiste relaties geconfigureerd om trapsgewijs te verwijderen. Optionele relaties zijn geconfigureerd om niet trapsgewijs te verwijderen.

Veel-op-veel

Veel-naar-veel-relaties hebben geen hoofd- en afhankelijke uiteinden, en geen van beide uiteinden bevat een vreemde-sleutel eigenschap. In plaats daarvan gebruiken veel-op-veel-relaties een join-entiteitstype dat paren van vreemde sleutels bevat die naar elk uiteinde van de veel-op-veel-relatie wijzen. Houd rekening met de volgende entiteitstypen, waarvoor een veel-op-veel-relatie wordt gedetecteerd volgens conventie:

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

De conventies die in deze detectie worden gebruikt, zijn:

  • Het type join-entiteit heeft de naam <left entity type name><right entity type name>. In PostTag dit voorbeeld dus.
    • De jointabel heeft dezelfde naam als het join-entiteitstype.
  • Het type join-entiteit krijgt een vreemde sleuteleigenschap voor elke richting van de relatie. Deze hebben de naam <navigation name><principal key name>. In dit voorbeeld zijn de eigenschappen van de refererende sleutel PostsId en TagsId.
    • Voor een unidirectionele veel-op-veel-relatie wordt de buitenlandse sleuteleigenschap zonder een bijbehorende navigatie <principal entity type name><principal key name> genoemd.
  • De eigenschappen van de vreemde sleutel zijn niet-null, waardoor beide relaties met de join-entiteit vereist zijn.
    • De conventies voor trapsgewijs verwijderen betekenen dat deze relaties worden geconfigureerd voor trapsgewijs verwijderen.
  • Het type koppelingsentiteit wordt geconfigureerd met een samengestelde primaire sleutel die bestaat uit de twee eigenschappen van de buitenlandse sleutels. In dit voorbeeld bestaat de primaire sleutel dus uit PostsId en TagsId.

Dit resulteert in het volgende EF-model:

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

En wordt omgezet in het volgende databaseschema bij het gebruik van 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");

Indexen

Volgens de conventie maakt EF voor de eigenschap of eigenschappen van een vreemde sleutel een database-index aan. Het type index dat is gemaakt, wordt bepaald door:

  • De kardinaliteit van de relatie
  • Of de relatie optioneel of vereist is
  • Het aantal eigenschappen waaruit de vreemde sleutel bestaat

Voor een een-op-veel-relatie wordt een eenvoudige index gemaakt op basis van conventies. Dezelfde index wordt gemaakt voor optionele en vereiste relaties. Bijvoorbeeld op SQLite:

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

Ofwel op SQL Server:

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

Voor een vereiste een-op-een-relatie wordt een unieke index gemaakt. Bijvoorbeeld op SQLite:

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

Of op SQL Server:

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

Voor optionele een-op-een-relaties is de index die is gemaakt op SQLite hetzelfde:

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

Op SQL Server wordt echter een IS NOT NULL filter toegevoegd om lege vreemde-sleutelwaarden beter te verwerken. Voorbeeld:

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

Voor samengestelde vreemde sleutels wordt een index gemaakt die alle kolommen van de vreemde sleutels omvat. Voorbeeld:

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

Opmerking

EF maakt geen indexen voor eigenschappen die al worden gedekt door een bestaande index- of primaire-sleutelbeperking.

Hoe te voorkomen dat EF indexen creëert voor vreemde sleutels

Indexen hebben overhead en, zoals hier wordt gevraagd, is het mogelijk niet altijd geschikt om ze te maken voor alle FK-kolommen. Om dit te bereiken, kan het ForeignKeyIndexConvention worden verwijderd bij het bouwen van het model:

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

Indien gewenst kunnen indexen nog steeds expliciet worden gemaakt voor die refererende-sleutelkolommen die deze wel nodig hebben.

Namen van vreemde-sleutelbeperkingen

Door conventie zijn foreign key beperkingen genoemd FK_<dependent type name>_<principal type name>_<foreign key property name>. Voor samengestelde buitenlandse sleutels, <foreign key property name> wordt een lijst gescheiden door onderstrepingstekens met eigenschappen van buitenlandse sleutels.

Aanvullende bronnen