Dela via


Konventioner för relationsidentifiering

EF Core använder en uppsättning konventioner när du identifierar och skapar en modell baserat på entitetstypklasser. Det här dokumentet sammanfattar de konventioner som används för att identifiera och konfigurera relationer mellan entitetstyper.

Viktigt!

De konventioner som beskrivs här kan åsidosättas genom explicit konfiguration av relationen med hjälp av antingen mappningsattribut eller modellskapande-API:et.

Tips/Råd

Koden nedan finns i RelationshipConventions.cs.

Upptäcka navigering

Relationsidentifieringen börjar med att identifiera navigeringer mellan entitetstyper .

Referensnavigeringar

En egenskap av en entitetstyp identifieras som en referensnavigering när:

  • Fastigheten är offentlig.
  • Boendet har en getter och en setter.
    • Setter behöver inte vara offentlig; det kan vara privat eller ha någon annan åtkomstnivå.
    • Settern kan endast vara Init.
  • Egenskapstypen är, eller kan vara, en entitetstyp. Det innebär att typen
    • Måste vara en referenstyp.
    • Får inte uttryckligen ha konfigurerats som en primitiv egenskapstyp.
    • Får inte mappas som en primitiv egenskapstyp av den databasprovider som används.
    • Får inte automatiskt konverteras till en primitiv egenskapstyp som mappas av databasprovidern som används.
  • Egenskapen är inte statisk.
  • Egenskapen är inte en indexerareegenskap.

Tänk till exempel på följande entitetstyper:

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 dessa typer upptäcks Blog.Author och Author.Blog som referensnavigeringar. Å andra sidan identifieras inte följande egenskaper som referensnavigeringar:

  • Blog.Id, eftersom int är en mappad primitiv typ
  • Blog.Title, eftersom "sträng" är en mappad primitiv typ
  • Blog.Uri, eftersom Uri konverteras automatiskt till en mappad primitiv typ
  • Blog.ConsoleKeyInfo, eftersom ConsoleKeyInfo är en C#-värdetyp
  • Blog.DefaultAuthor, eftersom egenskapen inte har någon setter
  • Author.Id, eftersom Guid är en mappad primitiv typ
  • Author.Name, eftersom "sträng" är en mappad primitiv typ
  • Author.BlogId, eftersom int är en mappad primitiv typ

Navigering i samlingar

En egenskap av en entitetstyp identifieras som en samlingsnavigering när:

  • Fastigheten är offentlig.
  • Boendet har en getter. Samlingsnavigeringar kan ha setters, men detta krävs inte.
  • Egenskapstypen är eller implementerar IEnumerable<TEntity>, där TEntity är eller kan vara en entitetstyp. Det innebär att typen av TEntity:
    • Måste vara en referenstyp.
    • Får inte uttryckligen ha konfigurerats som en primitiv egenskapstyp.
    • Får inte mappas som en primitiv egenskapstyp av den databasprovider som används.
    • Får inte automatiskt konverteras till en primitiv egenskapstyp som mappas av databasprovidern som används.
  • Egenskapen är inte statisk.
  • Egenskapen är inte en indexerareegenskap.

I följande kod identifieras till exempel både Blog.Tags och Tag.Blogs som samlingsnavigeringar:

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

Parkopplingsnavigeringar

När en navigering som går från till exempel entitetstyp A till entitetstyp B identifieras, måste den här gången avgöras om navigeringen har en invers som går i motsatt riktning, det vill säga från entitetstyp B till entitetstyp A. Om en sådan invers hittas kopplas de två navigeringarna ihop för att bilda en enda dubbelriktad relation.

Typen av relation bestäms av om navigeringen och dess invertering är referens- eller samlingsnavigeringar. Specifikt:

  • Om en navigering är en samlingsnavigering och den andra är en referensnavigering är relationen en-till-många.
  • Om båda navigeringerna är referensnavigeringar är relationen en-till-en.
  • Om båda navigeringerna är samlingsnavigeringar är relationen många-till-många.

Identifiering av var och en av dessa typer av relationer visas i exemplen nedan:

En enda en-till-många-relation identifieras mellan Blog och Post identifieras genom parkoppling av navigeringsfälten Blog.Posts och 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; }
}

En enda en-till-en-relation identifieras mellan Blog och Author identifieras genom parkoppling av navigeringsfälten Blog.Author och 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; }
}

En enda, många-till-många-relation identifieras mellan Post och Tag identifieras genom att länka navigeringsfälten Post.Tags och 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>();
}

Anmärkning

Den här parkopplingen av navigeringar kan vara felaktig om de två navigeringarna representerar två, olika, enkelriktade relationer. I det här fallet måste de två relationerna konfigureras explicit.

Parkoppling av relationer fungerar bara när det finns en enda relation mellan två typer. Flera relationer mellan två typer måste konfigureras explicit.

Anmärkning

Beskrivningarna här gäller relationer mellan två olika typer. Det är dock möjligt för samma typ att vara i båda ändar av en relation, och därför för en enda typ att ha två navigeringer som båda är kopplade till varandra. Detta kallas en självrefererande relation.

Upptäcka egenskaper hos främmande nyckel

När navigeringarna för en relation antingen har identifierats eller konfigurerats explicit används dessa för att upptäcka lämpliga egenskaper för foreign key för relationen. En egenskap identifieras som en främmande nyckel när:

  • Egenskapstypen är kompatibel med den primära eller alternativa nyckeln för huvudentitetstypen.
    • Typer är kompatibla om de är desamma, eller om egenskapstypen för främmande nyckel är en nullvärdig version av den primära eller alternativa nyckelegenskapstypen.
  • Egenskapsnamnet matchar en av namngivningskonventionerna för en främmande nyckelegenskap. Namngivningskonventionerna är:
    • <navigation property name><principal key property name>
    • <navigation property name>Id
    • <principal entity type name><principal key property name>
    • <principal entity type name>Id
  • Om den beroende änden dessutom uttryckligen har konfigurerats med hjälp av modellskapande-API:et och den beroende primärnyckeln är kompatibel, används även den beroende primärnyckeln som sekundärnyckel.

Tips/Råd

Suffixet "ID" kan ha valfritt hölje.

Följande entitetstyper visar exempel för var och en av dessa namngivningskonventioner.

Post.TheBlogKey identifieras som främmande nyckel eftersom den matchar mönstret <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 identifieras som främmande nyckel eftersom den matchar mönstret <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 identifieras som främmande nyckel eftersom den matchar mönstret <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 identifieras som främmande nyckel eftersom den matchar mönstret <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; }
}

Anmärkning

När det gäller en-till-många-navigeringar måste egenskaperna för främmande nyckeln finnas på typen med referensnavigeringen, eftersom detta blir den beroende entiteten. När det gäller en-till-en-relationer används identifiering av en främmande nyckelegenskap för att avgöra vilken typ som representerar den beroende delen av relationen. Om ingen sekundärnyckelegenskap identifieras måste den beroende änden konfigureras med HasForeignKey. Exempel på detta finns i En-till-en-relationer .

Reglerna ovan gäller även för sammansatta sekundärnycklar, där varje egenskap för den sammansatta måste ha en kompatibel typ med motsvarande egenskap för den primära eller alternativa nyckeln, och varje egenskapsnamn måste matcha någon av de namngivningskonventioner som beskrivs ovan.

Fastställa kardinalitet

EF använder de identifierade navigeringarna och foreign key-egenskaperna för att fastställa kardinaliteten för relationen tillsammans med dess huvudsakliga och beroende ändar.

  • Om det finns en oparad referensnavigering konfigureras relationen som enkelriktad en-till-många, med referensnavigering på den beroende änden.
  • Om det finns en oparad navigering för samlingen konfigureras relationen som en enkelriktad en-till-många med navigering för samlingen på huvudänden.
  • Om det finns länkade referens- och samlingsnavigeringar konfigureras relationen som en dubbelriktad en-till-många med samlingsnavigering i huvudänden.
  • Om en referensnavigering är kopplad till en annan referensnavigering:
    • Om en främmande nyckelegenskap identifierades på ena sidan men inte den andra, konfigureras relationen som en tvåvägs en-till-en, med främmande nyckelegenskapen på den beroende sidan.
    • I annat fall går det inte att fastställa den beroende sidan och EF utlöser ett undantag som anger att den beroende måste konfigureras uttryckligen.
  • Om en samlingsnavigering är kopplad till en annan samlingsnavigering konfigureras relationen som en dubbelriktad många-till-många.

Egenskaper för extern nyckel för skuggad

Om EF har fastställt det beroende slutet av relationen men ingen sekundärnyckelegenskap har identifierats skapar EF en skuggegenskap som representerar den externa nyckeln. Skuggegenskapen:

  • Har typen av den primära eller alternativa nyckelegenskapen vid den huvudsakliga änden av relationen.
    • Typen är null som standard, vilket gör relationen valfri som standard.
  • Om det finns en navigering på den beroende änden namnges egenskapen för skuggsekundärnyckeln med navigeringsnamnet sammanfogat med det primära eller alternativa nyckelegenskapsnamnet.
  • Om det inte finns någon navigering på den beroende sidan, namnges skuggan av främmande nyckelegenskap med entitetstypens namn, sammanfogat med det primära eller alternativa nyckelegenskapsnamnet.

Kaskadborttagning

Enligt konventionen konfigureras nödvändiga relationer för kaskadborttagning. Valfria relationer är konfigurerade för att inte utföra kaskad deletion.

Många-till-många

Många-till-många-relationer har inte huvud- och beroendeändar, och ingen av ändarna innehåller en egenskap för främmande nyckel. I stället använder många-till-många-relationer en kopplingsentitetstyp som innehåller par med sekundärnycklar som pekar på någon av slutpunkterna för många-till-många. Överväg följande entitetstyper, för vilka en många-till-många-relation identifieras av konventionen:

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 konventioner som används i den här identifieringen är:

  • Anslutningsentitetstypen heter <left entity type name><right entity type name>. Så i PostTag det här exemplet.
    • Kopplingstabellen har samma namn som anslutningsentitetstypen.
  • Kopplingsentiteten ges en utländsk nyckelegenskap för varje riktning i relationen. Dessa heter <navigation name><principal key name>. I det här exemplet är egenskaperna för främmande nyckel PostsId och TagsId.
    • För en enkelriktad många-till-många relation är den främmande nyckeln utan associerad navigering benämnd <principal entity type name><principal key name>.
  • Egenskaperna för främmande nyckel är icke-nullbara, vilket gör både relationerna till kopplingsentiteten nödvändiga.
    • Kaskadborttagningskonventionerna innebär att dessa relationer konfigureras för kaskadborttagning.
  • Anslutningsentitetstypen konfigureras med en sammansatt primärnyckel som består av de två foreign key-egenskaperna. I det här exemplet består alltså primärnyckeln av PostsId och TagsId.

Detta resulterar i följande 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

Och översätts till följande databasschema när du använder 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

Enligt konventionen skapar EF ett databasindex för egenskapen eller egenskaperna för en sekundärnyckel. Vilken typ av index som skapas bestäms av:

  • Kardinaliteten för relationen
  • Om relationen är valfri eller obligatorisk
  • Antalet egenskaper som utgör den främmande nyckeln

För en en-till-många-relation skapas ett enkelt index av konventionen. Samma index skapas för valfria och obligatoriska relationer. Till exempel på SQLite:

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

Eller på SQL Server:

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

För en obligatorisk en-till-en-relation skapas ett unikt index. Till exempel på SQLite:

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

Eller på SQL Sever:

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

För valfria en-till-en-relationer är indexet som skapats på SQLite detsamma:

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

Men på SQL Server läggs ett IS NOT NULL-filter till för att bättre hantera nullvärden för främmande nycklar. Till exempel:

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

För sammansatta sekundärnycklar skapas ett index som täcker alla sekundärnyckelkolumner. Till exempel:

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

Anmärkning

EF skapar inte index för egenskaper som redan omfattas av ett befintligt index eller primärnyckelvillkor.

Så här hindrar du EF från att skapa index för sekundärnycklar

Index har omkostnader, och som du frågar här kanske det inte alltid är lämpligt att skapa dem för alla FK-kolumner. För att uppnå detta ForeignKeyIndexConvention kan du ta bort när du skapar modellen:

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

Vid behov kan index fortfarande uttryckligen skapas för de främmande nyckelkolumner som behöver dem.

Namn på begränsningar för extern nyckel

Enligt konventionen heter begränsningar för utländska nycklar FK_<dependent type name>_<principal type name>_<foreign key property name>. För sammansatta främmande nycklar, <foreign key property name> blir en understrecksavgränsad lista över egenskapsnamn för främmande nyckel.

Ytterligare resurser