Vztahy 1:N
Relace 1:N se používají, když je jedna entita přidružená k libovolnému počtu dalších entit. Například Blog
může mít mnoho přidružených Posts
, ale každý Post
je přidružený pouze k jednomu Blog
.
Tento dokument je strukturovaný kolem mnoha příkladů. Příklady začínají běžnými případy, které také představují koncepty. Pozdější příklady pokrývají méně běžné druhy konfigurace. Dobrým přístupem je porozumět několika prvním příkladům a konceptům a pak přejít k pozdějším příkladům na základě vašich konkrétních potřeb. Na základě tohoto přístupu začneme jednoduchými "povinnými" a "volitelnými" relacemi 1:N.
Tip
Kód pro všechny níže uvedené příklady najdete v OneToMany.cs.
Požadováno 1:N
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Relace 1:N se skládá z:
- Jedna nebo více vlastností primárního nebo alternativního klíče u hlavní entity, tj. "jeden" konec relace. Například
Blog.Id
. - Jedna nebo více vlastností cizího klíče u závislé entity, tj. konec relace "N". Například
Post.BlogId
. - Volitelně můžete na hlavní entitu odkazující na závislé entity přidat navigaci kolekce. Například
Blog.Posts
. - Volitelně můžete odkazovat na závislou entitu odkazující na hlavní entitu. Například
Post.Blog
.
Proto pro relaci v tomto příkladu:
- Vlastnost
Post.BlogId
cizího klíče není nullable. Díky tomu je relace povinná, protože každá závislá (Post
) musí souviset s určitým objektem zabezpečení (Blog
), protože jeho vlastnost cizího klíče musí být nastavena na určitou hodnotu. - Obě entity mají navigace odkazující na související entitu nebo entity na druhé straně relace.
Poznámka:
Požadovaná relace zajišťuje, že každá závislá entita musí být přidružená k určité hlavní entitě. Hlavní entita však může existovat vždy bez závislých entit. To znamená, že povinný vztah neznamená, že vždy bude alespoň jedna závislá entita. V modelu EF neexistuje žádný způsob a také standardní způsob v relační databázi, aby se zajistilo, že je objekt zabezpečení přidružený k určitému počtu závislých položek. Pokud je to potřeba, musí se implementovat v aplikační (obchodní) logice. Další informace najdete v části Požadované navigace.
Tip
Relace se dvěma navigacemi, jednou ze závislých na objektu zabezpečení a inverzní z objektu zabezpečení do závislých objektů, se označuje jako obousměrná relace.
Tento vztah je zjištěn konvencí. To znamená:
Blog
je zjištěn jako objekt zabezpečení v relaci aPost
je zjištěn jako závislý.Post.BlogId
je zjištěn jako cizí klíč závislého odkazujícího naBlog.Id
primární klíč objektu zabezpečení. Relace se zjistí podle potřeby, protožePost.BlogId
není možné použít hodnotu null.Blog.Posts
je zjištěn jako navigace v kolekci.Post.Blog
je zjištěn jako referenční navigace.
Důležité
Při použití typů odkazů s možnou hodnotou null jazyka C# musí být navigace v odkazu null, pokud je vlastnost cizího klíče nullable. Pokud je vlastnost cizího klíče nenulová, může být odkazová navigace nullable nebo ne. V tomto případě Post.BlogId
je nenulovatelná a Post.Blog
je také nenulová. Konstruktor = null!;
se používá k označení tohoto jako záměrného pro kompilátor jazyka C#, protože EF obvykle nastaví Blog
instanci a pro plně načtenou relaci nemůže mít hodnotu null. Další informace najdete v tématu Práce s odkazovými typy s možnou hodnotou Null.
V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
V předchozím příkladu začíná konfigurace relací HasMany
u typu entity objektu zabezpečení (Blog
) a pak následuje s WithOne
. Stejně jako u všech relací je přesně ekvivalentní začínat závislým typem entity (Post
) a následně používat HasOne
WithMany
. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(e => e.Blog)
.WithMany(e => e.Posts)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Žádná z těchto možností není lepší než druhá; Oba mají za následek naprosto stejnou konfiguraci.
Tip
Nikdy není nutné konfigurovat relaci dvakrát, jednou od objektu zabezpečení a pak znovu začít ze závislého objektu. Pokus o konfiguraci objektu zabezpečení a závislých polovin relace obecně nefunguje. Zvolte, jestli chcete nakonfigurovat každou relaci z jednoho konce nebo druhé, a pak konfigurační kód zapište pouze jednou.
Volitelné 1:N
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int? BlogId { get; set; } // Optional foreign key property
public Blog? Blog { get; set; } // Optional reference navigation to principal
}
To je stejné jako v předchozím příkladu, s tím rozdílem, že vlastnost cizího klíče a navigace na objekt zabezpečení jsou nyní null. Díky tomu je relace "volitelná", protože závislá (Post
) může existovat , aniž by to souvisela s žádným objektem zabezpečení (Blog
).
Důležité
Při použití typů odkazů s možnou hodnotou null jazyka C# musí být navigace v odkazu null, pokud je vlastnost cizího klíče nullable. V tomto případě Post.BlogId
je možné použít hodnotu null, takže Post.Blog
musí být také s možnou hodnotou null. Další informace najdete v tématu Práce s odkazovými typy s možnou hodnotou Null.
Stejně jako předtím je tento vztah zjištěn konvencí. V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired(false);
}
Vyžadováno 1:N se stínovým cizím klíčem
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
V některých případech nemusí být v modelu požadována vlastnost cizího klíče, protože cizí klíče jsou podrobností o způsobu znázornění relace v databázi, která není nutná při použití relace čistě objektově orientovaným způsobem. Pokud se ale 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 beze změny, pokud entity nejsou ve formě objektu. 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.
Následující příklad z předchozích dvou příkladů odebere vlastnost cizího klíče ze závislého typu entity. EF proto vytvoří vlastnost stínového cizího klíče volaného BlogId
typu int
.
Tady je důležité poznamenat, že se používají typy odkazů s možnou hodnotou null jazyka C#, takže k určení, zda je vlastnost cizího klíče null nebo nikoli, se používá, a proto zda je relace volitelná nebo povinná. Pokud se nepoužívají odkazové typy s možnou hodnotou null, vlastnost stínového cizího klíče bude ve výchozím nastavení null, takže relace je ve výchozím nastavení volitelná. V tomto případě použijte IsRequired
k vynucení, aby vlastnost stínového cizího klíče byla nenulová a aby byla relace povinná.
Stejně jako předtím je tento vztah zjištěn konvencí. V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("BlogId")
.IsRequired();
}
Volitelný 1:N se stínovým cizím klíčem
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public Blog? Blog { get; set; } // Optional reference navigation to principal
}
Stejně jako v předchozím příkladu byla vlastnost cizího klíče odebrána ze závislého typu entity. EF proto vytvoří vlastnost stínového cizího klíče volaného BlogId
typu int?
. Na rozdíl od předchozího příkladu je tentokrát vlastnost cizího klíče vytvořena jako nullable, protože se používají odkazové typy c# s možnou hodnotou null a navigace na závislém typu entity je nullable. Díky tomu je relace volitelná.
Pokud se nepoužívají odkazové typy s možnou hodnotou null jazyka C#, vlastnost cizího klíče se ve výchozím nastavení vytvoří také jako nullable. To znamená, že relace s automaticky vytvořenými stínovými vlastnostmi jsou ve výchozím nastavení volitelné.
Stejně jako předtím je tento vztah zjištěn konvencí. V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasForeignKey("BlogId")
.IsRequired(false);
}
1:N bez navigace na objekt zabezpečení
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
}
V tomto příkladu byla vlastnost cizího klíče znovu zavedena, ale navigace na závislém objektu byla odebrána.
Tip
Relace pouze s jednou navigaci, jedna ze závislých na objektu zabezpečení nebo jedna z objektu zabezpečení do závislých položek, ale ne obě, se označuje jako jednosměrná relace.
Stejně jako předtím je tento vztah zjištěn konvencí. V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne()
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Všimněte si, že volání WithOne
nemá žádné argumenty. To je způsob, jak říct EF, že neexistuje žádná navigace od Post
.Blog
Pokud konfigurace začíná z entity bez navigace, musí být typ entity na druhém konci relace explicitně zadán pomocí obecného HasOne<>()
volání. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne<Blog>()
.WithMany(e => e.Posts)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
1:N bez navigace na objekt zabezpečení a se stínovým cizím klíčem
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
}
Tento příklad kombinuje dva z předchozích příkladů odebráním vlastnosti cizího klíče a navigace na závislém objektu.
Tato relace je zjištěna konvencí jako volitelná relace. Vzhledem k tomu, že v kódu není nic, co by bylo možné použít k označení, že by se mělo vyžadovat, je potřeba použít minimální konfiguraci IsRequired
k vytvoření požadované relace. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne()
.IsRequired();
}
Úplnou konfiguraci lze použít k explicitní konfiguraci názvu navigace a cizího klíče s odpovídajícím voláním IsRequired()
nebo IsRequired(false)
podle potřeby. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne()
.HasForeignKey("BlogId")
.IsRequired();
}
1:N bez navigace na závislé
// Principal (parent)
public class Blog
{
public int Id { get; set; }
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Předchozí dva příklady obsahovaly navigaci z objektu zabezpečení na závislé, ale žádná navigace ze závislého objektu na objekt zabezpečení. Pro další pár příkladů se navigace na závislém objektu znovu zavádí, zatímco navigace na objektu zabezpečení se odebere.
Stejně jako předtím je tento vztah zjištěn konvencí. V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(e => e.Blog)
.WithMany()
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
Znovu si všimněte, že WithMany()
se volá bez argumentů, aby bylo možné označit, že v tomto směru neexistuje žádná navigace.
Pokud konfigurace začíná z entity bez navigace, musí být typ entity na druhém konci relace explicitně zadán pomocí obecného HasMany<>()
volání. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany<Post>()
.WithOne(e => e.Blog)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
1:N bez navigace
Někdy může být užitečné nakonfigurovat relaci bez navigace. Takový vztah může být manipulován pouze změnou hodnoty cizího klíče přímo.
// Principal (parent)
public class Blog
{
public int Id { get; set; }
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
}
Tato relace není zjištěna konvencí, protože neexistují žádné navigace označující, že tyto dva typy souvisejí. Lze ho nakonfigurovat explicitně v OnModelCreating
souboru . Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany<Post>()
.WithOne();
}
Při této konfiguraci je vlastnost stále zjištěna jako cizí klíč podle konvence a relace je vyžadována, Post.BlogId
protože vlastnost cizího klíče není nullable. Vztah může být "volitelný" tím, že vlastnost cizího klíče je null.
Explicitnější konfigurace této relace je:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany<Post>()
.WithOne()
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
1:N s alternativním klíčem
Ve všech dosud uvedených příkladech je vlastnost cizího klíče na závislém objektu omezena na vlastnost primárního klíče objektu zabezpečení. Cizí klíč lze místo toho omezit na jinou vlastnost, která se pak stane alternativním klíčem pro typ entity objektu zabezpečení. Příklad:
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public int AlternateId { get; set; } // Alternate key as target of the Post.BlogId foreign key
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Tato relace není zjištěna konvencí, protože ef vždy vytvoří relaci s primárním klíčem. Lze ji nakonfigurovat explicitně pomocí OnModelCreating
volání HasPrincipalKey
. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => e.AlternateId);
}
HasPrincipalKey
lze kombinovat s dalšími voláními pro explicitní konfiguraci navigace, vlastností cizího klíče a povinné/volitelné povahy. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => e.AlternateId)
.HasForeignKey(e => e.BlogId)
.IsRequired();
}
1:N se složeným cizím klíčem
V dosud uvedených příkladech se primární nebo alternativní vlastnost klíče objektu zabezpečení skládala z jedné vlastnosti. Primární nebo alternativní klíče lze také vytvořit z více než jedné vlastnosti– označují se jako "složené klíče". Pokud má objekt zabezpečení relace složený klíč, musí být cizí klíč závislého klíče také složený klíč se stejným počtem vlastností. Příklad:
// Principal (parent)
public class Blog
{
public int Id1 { get; set; } // Composite key part 1
public int Id2 { get; set; } // Composite key part 2
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId1 { get; set; } // Required foreign key property part 1
public int BlogId2 { get; set; } // Required foreign key property part 2
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Tento vztah je zjištěn konvencí. Samotný složený klíč ale musí být nakonfigurovaný explicitně:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(e => new { e.Id1, e.Id2 });
}
Důležité
Složená hodnota cizího klíče se považuje za null
hodnotu, pokud některé z jejích hodnot vlastností mají hodnotu null. Složený cizí klíč s jednou vlastností null a jinou nenulovou hodnotou se nepovažuje za shodu primárního nebo alternativního klíče se stejnými hodnotami. Obě budou považovány za null
.
Obojí HasForeignKey
a HasPrincipalKey
lze je použít k explicitní zadání klíčů s více vlastnostmi. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(
nestedBuilder =>
{
nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });
nestedBuilder.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.HasPrincipalKey(e => new { e.Id1, e.Id2 })
.HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
.IsRequired();
});
}
Tip
Ve výše uvedeném kódu se volání HasKey
HasMany
seskupila do vnořeného tvůrce. Vnoření tvůrci odeberou potřebu volat Entity<>()
Entity<>()
vícekrát pro stejný typ entity, ale jsou funkčně ekvivalentní volání vícekrát.
Vyžadováno 1:N bez kaskádového odstranění
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}
// Dependent (child)
public class Post
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Podle konvence jsou požadované relace nakonfigurované tak, aby kaskádově odstraňovaly. To znamená, že při odstranění objektu zabezpečení se odstraní také všechny jeho závislé položky, protože závislé položky v databázi bez objektu zabezpečení neexistují. Ef je možné nakonfigurovat tak, aby místo automatického odstraňování závislých řádků, které už neexistují, vyvolala výjimku:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.OnDelete(DeleteBehavior.Restrict);
}
Odkazování na 1:N
Ve všech předchozích příkladech se typ hlavní entity liší od závislého typu entity. To nemusí být případ. Například v níže uvedených typech se každý z nich Employee
vztahuje k druhému Employees
.
public class Employee
{
public int Id { get; set; }
public int? ManagerId { get; set; } // Optional foreign key property
public Employee? Manager { get; set; } // Optional reference navigation to principal
public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}
Tento vztah je zjištěn konvencí. V případech, kdy se navigace, cizí klíč nebo povinná/volitelná povaha relace nezjistí konvencí, je možné tyto věci nakonfigurovat explicitně. Příklad:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOne(e => e.Manager)
.WithMany(e => e.Reports)
.HasForeignKey(e => e.ManagerId)
.IsRequired(false);
}