Cizí a hlavní klíče v relacích

Všechny relace 1:1 a 1:N jsou definovány cizím klíčem na závislém konci, který odkazuje na primární nebo alternativní klíč na konci objektu zabezpečení. Pro usnadnění tohoto vztahu se tento primární nebo alternativní klíč označuje jako "hlavní klíč". Relace M:N se skládají ze dvou relací 1:N, z nichž každá je definovaná cizím klíčem odkazujícím na hlavní klíč.

Tip

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

Cizí klíče

Vlastnost nebo vlastnosti, které tvoří cizí klíč, jsou často zjištěny konvencí. Vlastnosti lze také nakonfigurovat explicitně pomocí atributů mapování nebo HasForeignKey pomocí rozhraní API pro vytváření modelů. HasForeignKey lze použít s výrazem lambda. Například pro cizí klíč složený z jedné vlastnosti:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.ContainingBlogId);
}

Nebo pro složený cizí klíč složený z více než jedné vlastnosti:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}

Tip

Použití výrazů lambda v rozhraní API pro vytváření modelů zajišťuje, že je vlastnost k dispozici pro analýzu kódu a refaktoring, a také poskytuje typ vlastnosti rozhraní API pro použití v dalších zřetězených metodách.

HasForeignKey lze také předat název vlastnosti cizího klíče jako řetězec. Například pro jednu vlastnost:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId");
}

Nebo pro složený cizí klíč:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId1", "ContainingBlogId2");
}

Použití řetězce je užitečné v následujících případech:

  • Vlastnost nebo vlastnosti jsou soukromé.
  • Vlastnost nebo vlastnosti neexistují u typu entity a měly by být vytvořeny jako stínové vlastnosti.
  • Název vlastnosti se vypočítá nebo sestaví na základě určitého vstupu procesu sestavení modelu.

Sloupce cizího klíče bez hodnoty null

Jak je popsáno v volitelných a povinných relacích, nullability cizího klíče vlastnost určuje, zda je relace volitelná nebo povinná. Vlastnost cizího klíče s možnou hodnotou null však lze použít pro požadovanou relaci pomocí atributu [Required]nebo voláním IsRequired v rozhraní API pro vytváření modelů. Příklad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

Nebo pokud je cizí klíč zjištěn konvencí, IsRequired lze jej použít bez volání HasForeignKey:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .IsRequired();
}

Konečným výsledkem je, že sloupec cizího klíče v databázi není nullable, i když je vlastnost cizího klíče nullable. Totéž lze dosáhnout explicitně konfigurací vlastnosti cizího klíče podle potřeby. Příklad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property(e => e.BlogId)
        .IsRequired();
}

Stínové cizí klíče

Vlastnosti cizího klíče lze vytvořit jako stínové vlastnosti. V modelu EF existuje stínová vlastnost, ale v typu .NET neexistuje. EF sleduje hodnotu vlastnosti a stav interně.

Stínové cizí klíče se obvykle používají v případě, že chcete skrýt relační koncept cizího klíče z doménového modelu používaného aplikačním kódem nebo obchodní logikou. Tento kód aplikace pak manipuluje s relací zcela prostřednictvím navigace.

Tip

Pokud se entity budou serializovat, například odeslat přes drát, pak hodnoty cizího klíče mohou být užitečným způsobem, jak zachovat informace relace nedotčené, pokud entity nejsou ve formě objektu nebo grafu. Proto je pro tento účel často praktická udržovat vlastnosti cizího klíče v typu .NET. Vlastnosti cizího klíče můžou být soukromé, což je často dobrým kompromisem, aby se zabránilo zveřejnění cizího klíče a zároveň jeho hodnota mohla cestovat s entitou.

Vlastnosti stínového cizího klíče se často vytvářejí konvencí. Stínový cizí klíč bude vytvořen také v případě, že HasForeignKey argument neodpovídá žádné vlastnosti .NET. Příklad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Podle konvence získá stínový cizí klíč svůj typ z hlavního klíče v relaci. Tento typ je nullable, pokud relace není zjištěna jako nebo nakonfigurována podle potřeby.

Vlastnost stínového cizího klíče lze také vytvořit explicitně, což je užitečné pro konfiguraci omezujících vlastností vlastnosti. Chcete-li například nastavit vlastnost, která není nullable:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property<string>("MyBlogId")
        .IsRequired();

    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Tip

Podle konvence dědí vlastnosti cizího klíče omezující vlastnosti, jako je maximální délka a podpora Unicode z hlavního klíče v relaci. Proto je zřídka nutné explicitně konfigurovat omezující vlastnosti pro vlastnost cizího klíče.

Vytvoření stínové vlastnosti, pokud daný název neodpovídá žádné vlastnosti typu entity lze zakázat pomocí ConfigureWarnings. Příklad:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));

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.

To lze změnit v rozhraní API pro vytváření modelů pomocí HasConstraintName. Příklad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .HasConstraintName("My_BlogId_Constraint");
}

Tip

Název omezení nepoužívá modul runtime EF. Používá se pouze při vytváření schématu databáze pomocí migrací EF Core.

Indexy pro cizí klíče

Ef vytvoří index databáze pro vlastnost nebo vlastnosti cizího klíče. Další informace otypech

Tip

Relace jsou definovány v modelu EF mezi typy entit zahrnutými v daném modelu. Některé relace můžou potřebovat odkazovat na typ entity v modelu jiného kontextu – například při použití vzoru BoundedContext. V těchto situacích by se sloupce cizího klíče měly mapovat na normální vlastnosti a tyto vlastnosti je pak možné manipulovat ručně za účelem zpracování změn relace.

Hlavní klíče

Podle konvence jsou cizí klíče omezené na primární klíč na konci relace. Alternativní klíč se ale dá použít. Toho se dosahuje pomocí HasPrincipalKey rozhraní API pro vytváření modelů. Například pro jeden cizí klíč vlastnosti:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

Nebo pro složený cizí klíč s více vlastnostmi:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => new { e.AlternateId1, e.AlternateId2 });
}

HasPrincipalKey lze také předat název alternativní vlastnosti klíče jako řetězec. Například pro jeden klíč vlastnosti:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId");
}

Nebo pro složený klíč:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId1", "AlternateId2");
}

Poznámka:

Pořadí vlastností v objektu zabezpečení a cizím klíči se musí shodovat. Toto je také pořadí, ve kterém je klíč definován ve schématu databáze. Nemusí být stejné jako pořadí vlastností v typu entity nebo sloupcích v tabulce.

Není nutné volat HasAlternateKey k definování alternativního klíče pro entitu objektu zabezpečení. To se provádí automaticky při HasPrincipalKey použití s vlastnostmi, které nejsou vlastnostmi primárního klíče. HasAlternateKey Lze však použít k další konfiguraci alternativního klíče, například k nastavení názvu omezení databáze. Další informace najdete v tématu Klíče .

Vztahy s entitami bez klíčů

Každá relace musí mít cizí klíč, který odkazuje na hlavní klíč (primární nebo alternativní). To znamená, že typ entity bez klíčů nemůže fungovat jako hlavní konec relace, protože pro cizí klíče, na který se mají odkazovat, neexistuje žádný hlavní klíč.

Tip

Typ entity nemůže mít alternativní klíč, ale žádný primární klíč. V tomto případě musí být alternativní klíč (nebo jeden z alternativních klíčů, pokud jich existuje několik), upřednostněný na primární klíč.

Typy entit bez klíčů ale můžou mít stále definované cizí klíče, a proto můžou fungovat jako závislý konec relace. Představte si například tyto typy, kde Tag nemá žádný klíč:

public class Tag
{
    public string Text { get; set; } = null!;
    public int PostId { get; set; }
    public Post Post { get; set; } = null!;
}

public class Post
{
    public int Id { get; set; }
}

Tag lze nakonfigurovat na závislém konci relace:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Tag>()
        .HasNoKey();

    modelBuilder.Entity<Post>()
        .HasMany<Tag>()
        .WithOne(e => e.Post);
}

Poznámka:

EF nepodporuje navigace odkazující na typy entit bez klíčů. Viz Problém GitHubu č. 30331.

Cizí klíče v relacích M:N

V relacích M:N jsou cizí klíče definovány v typu entity spojení a mapovány na omezení cizího klíče v tabulce spojení. Všechno, co je popsáno výše, lze také použít u těchto cizích klíčů entity join. Například nastavení názvů omezení databáze:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
            r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}