diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.mdx b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.mdx index dc55283..888842f 100644 --- a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.mdx +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/ReadMe.mdx @@ -52,7 +52,7 @@ Ce qu'il faut noter : * ```Nounours``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_). * ```Information``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_). * ```Nounours``` possède une association vers ```Information``` -```csharp +```csharp title='Nounours.cs' public Information Information { get; set; @@ -64,7 +64,7 @@ public Information Information * Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. * ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```. -```csharp +```csharp title='NounoursDBEntities.cs' public DbSet NounoursSet { get; set; } ``` * Dans cet exemple, ```NounoursDBEntities``` __NE__ déclare __PAS__ de ```DbSet``` d'```Information``` (*toutefois, l'exemple fonctionnerait de la même manière si c'était le cas*). @@ -81,7 +81,7 @@ mais n'a pas été écrite par le développeur : on parle alors de **shadow prop * ```NounoursDBEntitiesWithStub``` est une classe fille de ```NounoursDBEntites```. Son rôle est de proposer un Stub en plus de ce que sait déjà faire sa classe mère. Elle ne sera donc utilisée que pour des tests unitaires ou fonctionnels. En conséquence, elle reprend tout ce que fait sa classe mère et ne change que la méthode ```OnModelCreating``` qui appelle la méthode de la classe mère puis ajoute des instances d'entités, grâce à la méthode d'extension ```HasData```. -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -102,13 +102,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } ``` Elle crée d'abord des entités d'```Information``` -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' var info1 = new Information { InformationId = 1, MadeBy = "George Lucas", MadeIn = "Kashyyyk" }; var info2 = new Information { InformationId = 2, MadeBy = "George Lucas", MadeIn = "Dagobah" }; var info3 = new Information { InformationId = 3, MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" }; ``` et les ajoute à la base : -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' modelBuilder.Entity().HasData(info1, info2, info3); ``` Elle va ensuite créer des instances de ```Nounours``` et les ajouter à la base, mais pour cela, il lui faut donner l'identifiant de l'```Information``` à lier au ```Nounours```. @@ -116,11 +116,11 @@ Nous sommes donc obligés dans ce cas de fournir la propriété ```InformationId Deux solutions existent alors : 1) soit vous rajoutez une propriété ```InformationId``` dans ```Nounours``` : mais c'est dommage d'avoir à le faire uniquement pour un stub. 2) soit vous rajoutez la **shadow property** directement dans cette méthode ```OnModelCreating``` avec la ligne suivante qui est alors équivalente et permet de rajouter une propriété à une entité : -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' modelBuilder.Entity().Property("InformationId"); ``` On peut ensuite créer les instances de ```Nounours``` et les relier aux instances de ```Information``` en utilisant la nouvelle propriété ```InformationId``` de ```Nounours``` correspondant à la clé primaire de ``` Information```. -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' modelBuilder.Entity().HasData( new { UniqueId = Guid.Parse("{4422C524-B2CB-43EF-8263-990C3CEA7CAE}"), Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567, InformationId = 1 }, new { UniqueId = Guid.Parse("{A4F84D92-C20F-4F2D-B3F9-CA00EF556E72}"), Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3, InformationId = 2 }, @@ -130,7 +130,7 @@ modelBuilder.Entity().HasData( ### La classe ```Program``` * ```NounoursDBEntitiesWithStub``` est ensuite utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire. -```csharp +```csharp title='Program.cs' using (NounoursDBEntities db = new NounoursDBEntities()) { WriteLine("Contenu de la base :"); @@ -143,16 +143,16 @@ using (NounoursDBEntities db = new NounoursDBEntities()) ``` Toutefois, si on ne précise pas qu'on veut également récupérer les contenus des propriétés ```Information``` des ```Nounours```, **EF Core** ne le fera pas et ne chargera que les propriétés simples des ```Nounours```. Pour forcer **EF Core** à charger également les données d'```Information```, on utilise la méthode d'extension ```Include``` : -```csharp +```csharp title='Program.cs' db.NounoursSet.Include(n => n.Information) ``` ```db.NounoursSet``` récupère les données de la table de ```Nounours``` et ```.Include(n => n.Information)``` permet de préciser qu'on veut également récupérer les entités d'```Information``` associées à ces ```Nounours```. * La suite de l'exemple ajoute un nouveau ```Nounours``` et affiche le contenu de la base de données. -```csharp +```csharp title='Program.cs' db.NounoursSet.Add(new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 07, 19), NbPoils = 123, Information = new Information { MadeIn = "Ahch-To", MadeBy = "Jake Lunt Davies" } }); db.SaveChangesAsync(); ``` -```csharp +```csharp title='Program.cs' using (NounoursDBEntities db = new NounoursDBEntities()) { WriteLine("Contenu de la base :"); diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/ex_042_010_classDiagram.svg b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/ex_042_010_classDiagram.svg deleted file mode 100644 index 449ef02..0000000 --- a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/readme_files/ex_042_010_classDiagram.svg +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Nounours - - - - - - - - +UniqueId: Guid - - - - - - - - +Nom: string - - - - - - - - +DateDeNaissance: DateTime - - - - - - - - +NbPoils: int - - - - - - - - - - - - - - - - - - - - - - - Information - - - - - - - - +InformationId: int - - - - - - - - +MadeBy: string - - - - - - - - +MadeIn: string - - - - - - - - - - - - - - +Information - - - - - - - - 1 - - - - - diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_02_SinglePropertyNavigation_fluentAPI.mdx b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_02_SinglePropertyNavigation_fluentAPI.mdx index a914931..55eb3cc 100644 --- a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_02_SinglePropertyNavigation_fluentAPI.mdx +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_02_SinglePropertyNavigation_fluentAPI.mdx @@ -52,7 +52,7 @@ Ce qu'il faut noter : * ```Nounours``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_). * ```Information``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_). * ```Nounours``` possède une association vers ```Information``` -```csharp +```csharp title='Nounours.cs' public Information Information { get; set; @@ -64,12 +64,12 @@ public Information Information * Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. * ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```. -```csharp +```csharp title='NounoursDBEntities.cs' public DbSet NounoursSet { get; set; } ``` * Dans cet exemple, ```NounoursDBEntities``` __NE__ déclare __PAS__ de ```DbSet``` d'```Information``` (*toutefois, l'exemple fonctionnerait de la même manière si c'était le cas*). * La méthode ```OnModelCreating``` est réécrite et écrasera donc une partie des *conventions d'écriture* ou des *annotations de données*. Ici, par exemple, elle donne quelques indications sur la classe ```Nounours``` comme nous l'avons déjà vu dans les exemples précédents : -```csharp +```csharp title='NounoursDBEntities.cs' protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().HasKey(n => n.UniqueId); @@ -83,7 +83,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } ``` * Puis, ce qui nous intéresse plus particulièrement ici, elle définit la relation entre ```Nounours``` et ```Information``` : une entité ```Nounours``` possède une entité ```Information``` sans navigation inverse. -```csharp +```csharp title='NounoursDBEntities.cs' modelBuilder.Entity().HasOne(n => n.Information); ``` ##### Quelques explications supplémentaires : @@ -99,7 +99,7 @@ mais n'a pas été écrite par le développeur : on parle alors de **shadow prop * ```NounoursDBEntitiesWithStub``` est une classe fille de ```NounoursDBEntites```. Son rôle est de proposer un Stub en plus de ce que sait déjà faire sa classe mère. Elle ne sera donc utilisée que pour des tests unitaires ou fonctionnels. En conséquence, elle reprend tout ce que fait sa classe mère et ne change que la méthode ```OnModelCreating``` qui appelle la méthode de la classe mère puis ajoute des instances d'entités, grâce à la méthode d'extension ```HasData```. -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -120,13 +120,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } ``` Elle crée d'abord des entités d'```Information``` -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' var info1 = new Information { InformationId = 1, MadeBy = "George Lucas", MadeIn = "Kashyyyk" }; var info2 = new Information { InformationId = 2, MadeBy = "George Lucas", MadeIn = "Dagobah" }; var info3 = new Information { InformationId = 3, MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" }; ``` et les ajoute à la base : -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' modelBuilder.Entity().HasData(info1, info2, info3); ``` Elle va ensuite créer des instances de ```Nounours``` et les ajouter à la base, mais pour cela, il lui faut donner l'identifiant de l'```Information``` à lier au ```Nounours```. @@ -134,11 +134,11 @@ Nous sommes donc obligés dans ce cas de fournir la propriété ```InformationId Deux solutions existent alors : 1) soit vous rajoutez une propriété ```InformationId``` dans ```Nounours``` : mais c'est dommage d'avoir à le faire uniquement pour un stub. 2) soit vous rajoutez la **shadow property** directement dans cette méthode ```OnModelCreating``` avec la ligne suivante qui est alors équivalente et permet de rajouter une propriété à une entité : -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' modelBuilder.Entity().Property("InformationId"); ``` On peut ensuite créer les instances de ```Nounours``` et les relier aux instances de ```Information``` en utilisant la nouvelle propriété ```InformationId``` de ```Nounours``` correspondant à la clé primaire de ``` Information```. -```csharp +```csharp title='NounoursDBEntitiesWithStub.cs' modelBuilder.Entity().HasData( new { UniqueId = Guid.Parse("{4422C524-B2CB-43EF-8263-990C3CEA7CAE}"), Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567, InformationId = 1 }, new { UniqueId = Guid.Parse("{A4F84D92-C20F-4F2D-B3F9-CA00EF556E72}"), Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3, InformationId = 2 }, @@ -148,7 +148,7 @@ modelBuilder.Entity().HasData( ### La classe ```Program``` * ```NounoursDBEntitiesWithStub``` est ensuite utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire. -```csharp +```csharp title='Program.cs' using (NounoursDBEntities db = new NounoursDBEntities()) { WriteLine("Contenu de la base :"); @@ -161,16 +161,16 @@ using (NounoursDBEntities db = new NounoursDBEntities()) ``` Toutefois, si on ne précise pas qu'on veut également récupérer les contenus des propriétés ```Information``` des ```Nounours```, **EF Core** ne le fera et ne chargera que les propriétés simples des ```Nounours```. Pour forcer **EF Core** à charger également les données d'```Information```, on utilise la méthode d'extension ```Include``` : -```csharp +```csharp title='Program.cs' db.NounoursSet.Include(n => n.Information) ``` ```db.NounoursSet``` récupère les données de la table de ```Nounours``` et ```.Include(n => n.Information)``` permet de préciser qu'on veut également récupérer les entités d'```Information``` associées à ces ```Nounours```. * La suite de l'exemple ajoute un nouveau ```Nounours``` et affiche le contenu de la base de données. -```csharp +```csharp title='Program.cs' db.NounoursSet.Add(new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 07, 19), NbPoils = 123, Information = new Information { MadeIn = "Ahch-To", MadeBy = "Jake Lunt Davies" } }); db.SaveChangesAsync(); ``` -```csharp +```csharp title='Program.cs' using (NounoursDBEntities db = new NounoursDBEntities()) { WriteLine("Contenu de la base :"); diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/ReadMe.mdx b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/ReadMe.mdx new file mode 100644 index 0000000..a9d00b2 --- /dev/null +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/ReadMe.mdx @@ -0,0 +1,243 @@ +--- +sidebar_label: '2.10.3. One to One with data annotations' +sidebar_position: 4 +description: "explique comment préparer un One to One avec les annotations de données" +--- +import Mermaid from '@theme/Mermaid'; + +# One to One relationships with data annotations + +*20/01/2020 ⋅ Marc Chevaldonné* +[**▶ Browse Sample ↗**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_012_OneToOne_conventions) + +--- + +Cet exemple montre comment réaliser une relation *One To One* entre deux entités avec *Entity Framework Core* et l'*annotation de données*. +Une version équivalente réalisée avec la *Fluent API* est disponible dans l'exemple +[**2.10.4. One To One with Fluent API**](/docs/Entity-Framework/Model/Relationships/02_10_04_OneToOne_FluentAPI) + +--- + +## Comment est construit cet exemple ? +* Le projet est de type .NET Core +* Il contient quatre classes : + * ```Nounours``` + * ```CarnetDeSante``` + * ```NounoursDBEntities``` + * ```StubbedContext``` + +### Les classes entités : ```Nounours``` et ```CarnetDeSante``` + +Un ```Nounours``` contient différentes propriétés (```Nom```, ```DateDeNaissance```, ```NbPoils```) dont ```Carnet``` de type ```CarnetDeSante```. +Un ```CarnetDeSante``` possède une propriété de type ```DateTime``` (```LastModified```), et une propriété ```Owner``` de type ```Nounours```. +On a donc bien une relation *One To One* puisqu'un ```Nounours``` possède un ```CarnetDeSante``` et qu'un ```CarnetDeSante``` possède un ```Nounours```. + + + +Ce qu'il faut noter : +* ```Nounours``` possède une association vers ```CarnetDeSante``` +```csharp title='Nounours.cs' +public CarnetDeSante Carnet { get; set; } +``` +* ```CarnetDeSante``` possède une association vers ```Nounours``` +```csharp title='CarnetDeSante.cs' +public Nounours Owner { get; set; } +``` +* ```Nounours``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _annotation de données_) : ```[Key]```. +* Cet identifiant est généré par la base : ```[DatabaseGenerated(DatabaseGeneratedOption.Identity)]```. +* ```CarnetDeSante``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _annotation de données_) : ```[Key]```. +* Cet identifiant fait référence à une clé étrangère qui est la clé primaire associée à l'entité pointée par sa propriété ```Owner``` : ```ForeignKey("Owner")```. +```csharp title='CarnetDeSante.cs' +[Key, ForeignKey("Owner")] +public int UniqueId +{ + get; set; +} +``` +* En conséquence, l'identifiant unique ```UniqueId``` de ```CarnetDeSante``` aura la même valeur que l'```UniqueId``` d'une entité ```Nounours```. + + +### La classe ```NounoursDBEntities``` + +* Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. +* ```NounoursDBEntities``` déclare deux ```DbSet``` : un de ```Nounours``` et l'autre de ```CarnetDeSante```. +```csharp title='NounoursDBEntities.cs' +public DbSet NounoursSet { get; set; } +public DbSet Carnets { get; set; } +``` +##### Quelques explications supplémentaires : +Pour pouvoir relier une entité ```Nounours``` à une entité ```CarnetDeSante``` de manière bidirectionnelle, les propriétés ```Nounours.Carnet``` et ```CarnetDeSante.Owner``` sont suffisantes, à condition qu'une clé étrangère soit déclarée et utilisée. +C'est le cas grâce à l'annotation de données ```ForeignKey("Owner")``` qui s'occupe de définir de quelle manière les deux entités sont reliées. + +### La classe ```StubbedContext``` + +* ```StubbedContext``` est une classe fille de ```NounoursDBEntities```. +Son rôle est de proposer un Stub en plus de ce que sait déjà faire sa classe mère. Elle ne sera donc utilisée que pour des tests unitaires ou fonctionnels. +En conséquence, elle reprend tout ce que fait sa classe mère et ne change que la méthode ```OnModelCreating``` qui appelle la méthode de la classe mère puis ajoute des instances d'entités, grâce à la méthode d'extension ```HasData```. +```csharp title='StubbedContext.cs' +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasData( + new CarnetDeSante { UniqueId=1, LastModified = DateTime.Today }, + new CarnetDeSante { UniqueId=2, LastModified = new DateTime(1980, 5, 21) }, + new CarnetDeSante { UniqueId=3, LastModified = new DateTime(1983, 5, 25) } + ); + + modelBuilder.Entity().HasData( + new Nounours { UniqueId=1, Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 }, + new Nounours { UniqueId=2, Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 }, + new Nounours { UniqueId=3, Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 } + ); +} +``` +* Remarquez que __À AUCUN MOMENT__ nous ne précisons les valeurs des propriétés ```Owner``` de ```CarnetDeSante``` et ```Carnet``` de ```Nounours```. +Le simple fait d'utiliser la même clé (propriétés ```UniqueId```) est suffisant puisque celle de ```CarnetDeSante``` est une clé étrangère. +* Notez que ce ne sera pas le cas lors d'une utilisation *classique* de nos classes (ajout, modification...). Nous ne donnerons plus les identifiants directement mais les références des propriétés ```Carnet``` et ```Owner```. + +### La classe ```Program``` +* La classe ```StubbedContext``` est ensuite indirectement utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire : ceci est fait lors de la migration et la création de la base. +* On affiche tout d'abord le contenu de la base (c'est-à-dire rien ou d'anciennes données si la migration est faite à partir de ```NounoursDBEntites```) ou le stub (si la migration est faite à partir de ```StubbedContext```). +:::info Include +Notez l'utilisation d'```Include``` dans ```db.NounoursSet.Include(n => n.Carnet)``` sinon, les ```CarnetDeSante``` ne sont pas chargés. ```Include``` n'est pas utilisé ensuite dans ```db.Carnets``` car les ```Nounours``` ont déjà été chargés depuis la connexion. Mais on aurait pu faire les accès dans l'autre sens et dans ce cas d'abord ```db.Carnets.Include(c => c.Owner)``` puis simplement ```db.NounoursSet```. +::: +```csharp title='Program.cs' +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + WriteLine("Contenu de la base (nounours) : "); + foreach (var n in db.NounoursSet.Include(n => n.Carnet)) + { + WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}"); + } + + WriteLine("Contenu de la base (carnets de santé) : "); + foreach (var c in db.Carnets) + { + WriteLine($"\t{c}"); + } + + //... +} +``` +* La suite de l'exemple ajoute un nouveau ```Nounours``` et son ```CarnetDeSante``` puis affiche le contenu de la base de données. +```csharp title='Program.cs' +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + //... + + WriteLine("\nAjout d'un nounours et de son carnet de santé\n"); + + Nounours porg = new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 7, 19), NbPoils = 123 }; + CarnetDeSante carnetPorg = new CarnetDeSante { LastModified = DateTime.Now, Owner = porg }; + porg.Carnet = carnetPorg; + + db.AddRange(porg, carnetPorg); + + db.SaveChanges(); +} +``` +```csharp title='Program.cs' +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + WriteLine("Contenu de la base (nounours) : "); + foreach (var n in db.NounoursSet.Include(n => n.Carnet)) + { + WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}"); + } + + WriteLine("Contenu de la base (carnets de santé) : "); + foreach (var c in db.Carnets) + { + WriteLine($"\t{c}"); + } +} +``` +## Comment exécuter cet exemple ? +Pour tester cette application, n'oubliez pas les commandes comme présentées dans l'exemple ex_041_001 : pour générer l'exemple, il vous faut d'abord préparer les migrations et les tables. + * Ouvrez la *Console du Gestionnaire de package* sous Windows ou le *Terminal* sous MacOSX, pour cela, dirigez-vous dans le menu *Outils*, puis *Gestionnaire de package NuGet*, puis *Console du Gestionnaire de package*. + * Dans la console que vous venez d'ouvrir, déplacez-vous dans le dossier du projet .NET Core, ici : +``` +cd .\p08_BDD_EntityFramework\ex_042_012_OneToOne_conventions +``` +:::note Note + Si vous n'avez pas installé correctement EntityFrameworkCore, il vous faudra peut-être utiliser également : + +* ```dotnet tool install --global dotnet-ef``` si vous utilisez la dernière version de .NET Core (3.1 aujourd'hui), + +* ```dotnet tool install --global dotnet-ef --version 3.0.0``` si vous vous utiliser spécifiquement .NET Core 3.0. +::: + + * Migration : +``` +dotnet ef migrations add ex_042_012 --context StubbedContext +``` + * Création de la table : +``` +dotnet ef database update --context StubbedContext +``` + * Génération et exécution +Vous pouvez maintenant générer et exécuter l'exemple [**ex_042_012_OneToOne_conventions ↗**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_012_OneToOne_conventions). + + * Le résultat de l'exécution va ressembler à : +``` +Contenu de la base (nounours) : + 1: Chewbacca (27/05/1977, 1234567 poils), LastModified: 20/01/2020 + 2: Yoda (21/05/1980, 3 poils), LastModified: 21/05/1980 + 3: Ewok (25/05/1983, 3456789 poils), LastModified: 25/05/1983 +Contenu de la base (carnets de santé) : + 1 : carnet de Chewbacca, modifié la dernière fois le 20/01/2020 + 2 : carnet de Yoda, modifié la dernière fois le 21/05/1980 + 3 : carnet de Ewok, modifié la dernière fois le 25/05/1983 + +Ajout d'un nounours et de son carnet de santé + +Contenu de la base (nounours) : + 1: Chewbacca (27/05/1977, 1234567 poils), LastModified: 20/01/2020 + 2: Yoda (21/05/1980, 3 poils), LastModified: 21/05/1980 + 3: Ewok (25/05/1983, 3456789 poils), LastModified: 25/05/1983 + 4: Porg (19/07/2017, 123 poils), LastModified: 20/01/2020 +Contenu de la base (carnets de santé) : + 1 : carnet de Chewbacca, modifié la dernière fois le 20/01/2020 + 2 : carnet de Yoda, modifié la dernière fois le 21/05/1980 + 3 : carnet de Ewok, modifié la dernière fois le 25/05/1983 + 4 : carnet de Porg, modifié la dernière fois le 20/01/2020 +``` +:::note Note +L'identifiant du dernier ```Nounours``` sera vraisemblablement différent puisqu'il est créé par la base lors de l'insertion. +::: + +## Comment exécuter cet exemple sans le stub ? +Il suffit de faire exactement comme dans le paragraphe précédent, mais en choisissant le contexte ```NounoursDBEntities``` à la place de ```StubbedContext``` : +``` +dotnet ef migrations add ex_042_012 --context NounoursDBEntities +dotnet ef database update --context NounoursDBEntities +``` +Lors de l'exécution, le résultat sera évidemment différent puisqu'il n'y aura pas les 3 nounours du stub. + +## Comment vérifier quelles base et tables ont été créées et leur contenu ? +Pour vérifier le contenu de votre base SQLite, vous pouvez utiliser le programme *DB Browser* : +* Rendez-vous sur la page : https://sqlitebrowser.org/dl/ et téléchargez le programme *DB Browser*. +* Lancez *DB Browser for SQLite* +* Glissez-déposez au milieu de la fenêtre de *DB Browser for SQLite* le fichier *ex_042_012_OneToOne_conventions.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_042_012_OneToOne_conventions.csproj*. +![DB Browser for SQLite](./readme_files/dbbrowser01.png) +* Choisissez ensuite l'onglet *Parcourir les données* +* Observez les résultats obtenus des deux tables +![DB Browser for SQLite](./readme_files/dbbrowser02.png) +![DB Browser for SQLite](./readme_files/dbbrowser03.png) + + diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser01.png b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser01.png new file mode 100644 index 0000000..d1b2396 Binary files /dev/null and b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser01.png differ diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser02.png b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser02.png new file mode 100644 index 0000000..a6c0765 Binary files /dev/null and b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser02.png differ diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser03.png b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser03.png new file mode 100644 index 0000000..f639bdf Binary files /dev/null and b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser03.png differ diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/ex_042_012_classDiagram.svg b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/ex_042_012_classDiagram.svg new file mode 100644 index 0000000..dbbbf09 --- /dev/null +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/readme_files/ex_042_012_classDiagram.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + Nounours + + + + + + + + +UniqueId: int + + + + + + + + +Nom: string + + + + + + + + +DateDeNaissance: DateTime + + + + + + + + +NbPoils: int + + + + + + + + + + + + + + + + + + + + + + + CarnetDeSante + + + + + + + + +UniqueId: int + + + + + + + + +LastModified: DateTime + + + + + + + + + + + +Owner + + + + + + + + 1 + + + + + + + + +Carnet + + + + + + + + 1 + + + + + diff --git a/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_04_OneToOne_FluentAPI.mdx b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_04_OneToOne_FluentAPI.mdx new file mode 100644 index 0000000..fa1e587 --- /dev/null +++ b/Documentation/docusaurus/docs/Entity-Framework/Model/Relationships/02_10_04_OneToOne_FluentAPI.mdx @@ -0,0 +1,304 @@ +--- +sidebar_label: '2.10.4. One to One with Fluent API' +sidebar_position: 5 +description: "explique comment préparer un One to One avec la Fluent API" +--- +import Mermaid from '@theme/Mermaid'; + +# One to One relationships with Fluent API + +*20/01/2020 ⋅ Marc Chevaldonné* +[**▶ Browse Sample ↗**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_013_OneToOne_FluentAPI) + +--- + +Cet exemple montre comment réaliser une relation *One To One* entre deux entités avec *Entity Framework Core* et la *Fluent API*. +Une version équivalente réalisée avec les *annotations de données* est disponible dans l'exemple +[**2.10.3. One To One with data annotations**](/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations/) + +--- + +## Comment est construit cet exemple ? +* Le projet est de type .NET Core +* Il contient quatre classes : + * ```Nounours``` + * ```CarnetDeSante``` + * ```NounoursDBEntities``` + * ```StubbedContext``` + +### Les classes entités : ```Nounours``` et ```CarnetDeSante``` + +Un ```Nounours``` contient différentes propriétés (```Nom```, ```DateDeNaissance```, ```NbPoils```) dont ```Carnet``` de type ```CarnetDeSante```. +Un ```CarnetDeSante``` possède une propriété de type ```DateTime``` (```LastModified```), et une propriété ```Owner``` de type ```Nounours```. +On a donc bien une relation *One To One* puisqu'un ```Nounours``` possède un ```CarnetDeSante``` et qu'un ```CarnetDeSante``` possède un ```Nounours```. + + + +Ce qu'il faut noter : +* ```Nounours``` possède une association vers ```CarnetDeSante``` +```csharp title='Nounours.cs' +public CarnetDeSante Carnet { get; set; } +``` +* ```CarnetDeSante``` possède une association vers ```Nounours``` +```csharp title='CarnetDeSante.cs' +public Nounours Owner { get; set; } +``` +* ```Nounours``` possède un identifiant unique ```UniqueId```. +* ```CarnetDeSante``` possède un identifiant unique ```UniqueId```. + + +### La classe ```NounoursDBEntities``` + +* Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. +* ```NounoursDBEntities``` déclare deux ```DbSet``` : un de ```Nounours``` et l'autre de ```CarnetDeSante```. +```csharp title='NounoursDBEntities.cs' +public DbSet NounoursSet { get; set; } +public DbSet Carnets { get; set; } +``` +* La classe réécrit ensuite la méthode ```OnModelCreating``` : +```csharp title='NounoursDBEntities.cs' +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + //création de la table TableNounours + modelBuilder.Entity().ToTable("TableNounours"); //nom de la table + modelBuilder.Entity().HasKey(n => n.UniqueId); //définition de la clé primaire + modelBuilder.Entity().Property(n => n.UniqueId) + .ValueGeneratedOnAdd(); //définition du mode de génération de la clé : génération à l'insertion + modelBuilder.Entity().Property(n => n.Nom).IsRequired() + .HasMaxLength(256); //définition de la colonne Nom + modelBuilder.Entity().Property(n => n.DateDeNaissance).HasColumnName("Naissance").HasColumnType("date"); //changement du nom de la colonne Naissance + + //création de la table "Carnets" + modelBuilder.Entity().ToTable("Carnets"); // nom de la table + modelBuilder.Entity().HasKey(c => c.UniqueId); //définition de la clé primaire + modelBuilder.Entity().Property(c => c.UniqueId) + .ValueGeneratedNever(); // définition du mode de génération de la clé : pas de génération automatique + //note : la colonne LastModified n'est pas touchée : utilisation des conventions EF + + + //on précise qu'il y a une relation entre CarnetDeSante et Nounours + modelBuilder.Entity() //l'entité Nounours... + .HasOne(n => n.Carnet) //a une propriété obligatoire Carnet... + .WithOne(c => c.Owner) //reliée à la propriété Owner du Carnet... + .HasForeignKey(c => c.UniqueId);//dont la propriété UniqueId est une Foreign Key + //remplace la ForeignKey + + base.OnModelCreating(modelBuilder); +} +``` +Voyons cette méthode plus en détails. +Tout d'abord, il y a la définition des détails de la table de ```Nounours``` : +* le changement de nom de la table : +```csharp title='NounoursDBEntities.cs' +modelBuilder.Entity().ToTable("TableNounours"); +``` +* la définition de la clé primaire : +```csharp title='NounoursDBEntities.cs' +modelBuilder.Entity().HasKey(n => n.UniqueId); +modelBuilder.Entity().Property(n => n.UniqueId) + .ValueGeneratedOnAdd(); +``` +* définition de contraintes sur les colonnes de ```Nounours``` : +```csharp title='NounoursDBEntities.cs' +modelBuilder.Entity().Property(n => n.Nom) + .IsRequired() + .HasMaxLength(256); +modelBuilder.Entity().Property(n => n.DateDeNaissance) + .HasColumnName("Naissance") + .HasColumnType("date"); +``` +On continue avec la définition des détails de la table de ```CarnetDeSante``` : +* le changement de nom de la table : +```csharp title='NounoursDBEntities.cs' +modelBuilder.Entity().ToTable("Carnets"); +``` +* la définition de la clé primaire (*notez qu'on ne demande pas ici à la base de générer la clé, puisqu'on va utiliser une clé étrangère*) : +```csharp title='NounoursDBEntities.cs' +modelBuilder.Entity().HasKey(c => c.UniqueId); +modelBuilder.Entity().Property(c => c.UniqueId) + .ValueGeneratedNever(); +``` +On s'intéresse enfin à la **relation _One To One_**, où l'on précise qu'une entité ```Nounours``` possède une association vers l'entité de type ```CarnetDeSante``` grâce à la propriété ```Carnet``` (```.HasOne(n => n.Carnet)```). +Cette entité ```CarnetDeSante``` possède elle-même une association vers une entité de type ```Nounours``` grâce à sa propriété ```Owner``` (```.WithOne(c => c.Owner)```), +et on précise que cette entité ```CarnetDeSante``` voit sa propriété ```UniqueId``` utilisée comme clé étrangère (```.HasForeignKey(c => c.UniqueId)```). Elle automatiquement reliée à la clé primaire de ```Nounours```. +```csharp title='NounoursDBEntities.cs' +modelBuilder.Entity() //l'entité Nounours... + .HasOne(n => n.Carnet) //a une propriété obligatoire Carnet... + .WithOne(c => c.Owner) //reliée à la propriété Owner du Carnet... + .HasForeignKey(c => c.UniqueId);//dont la propriété UniqueId est une Foreign Key +``` +Pour pouvoir relier une entité ```Nounours``` à une entité ```CarnetDeSante``` de manière bidirectionnelle, les propriétés ```Nounours.Carnet``` et ```CarnetDeSante.Owner``` sont suffisantes, à condition qu'une clé étrangère soit déclarée et utilisée. +C'est le cas grâce au code précédent qui s'occupe de définir de quelle manière les deux entités sont reliées. + +### La classe ```StubbedContext``` + +* ```StubbedContext``` est une classe fille de ```NounoursDBEntities```. +Son rôle est de proposer un Stub en plus de ce que sait déjà faire sa classe mère. Elle ne sera donc utilisée que pour des tests unitaires ou fonctionnels. +En conséquence, elle reprend tout ce que fait sa classe mère et ne change que la méthode ```OnModelCreating``` qui appelle la méthode de la classe mère puis ajoute des instances d'entités, grâce à la méthode d'extension ```HasData```. +```csharp title='StubbedContext.cs' +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasData( + new CarnetDeSante { UniqueId=1, LastModified = DateTime.Today }, + new CarnetDeSante { UniqueId=2, LastModified = new DateTime(1980, 5, 21) }, + new CarnetDeSante { UniqueId=3, LastModified = new DateTime(1983, 5, 25) } + ); + + modelBuilder.Entity().HasData( + new Nounours { UniqueId=1, Nom = "Chewbacca", DateDeNaissance = new DateTime(1977, 5, 27), NbPoils = 1234567 }, + new Nounours { UniqueId=2, Nom = "Yoda", DateDeNaissance = new DateTime(1980, 5, 21), NbPoils = 3 }, + new Nounours { UniqueId=3, Nom = "Ewok", DateDeNaissance = new DateTime(1983, 5, 25), NbPoils = 3456789 } + ); +} +``` +* Remarquez que __À AUCUN MOMENT__ nous ne précisons les valeurs des propriétés ```Owner``` de ```CarnetDeSante``` et ```Carnet``` de ```Nounours```. +Le simple fait d'utiliser la même clé (propriétés ```UniqueId```) est suffisant puisque celle de ```CarnetDeSante``` est une clé étrangère. +* Notez que ce ne sera pas le cas lors d'une utilisation *classique* de nos classes (ajout, modification...). Nous ne donnerons plus les identifiants directement mais les références des propriétés ```Carnet``` et ```Owner```. + +### La classe ```Program``` +* La classe ```StubbedContext``` est ensuite indirectement utilisée dans ```Program``` pour remplir la base de manière tout à fait classique et ne nécessite aucun appel supplémentaire : ceci est fait lors de la migration et la création de la base. +* On affiche tout d'abord le contenu de la base (c'est-à-dire rien ou d'anciennes données si la migration est faite à partir de ```NounoursDBEntites```) ou le stub (si la migration est faite à partir de ```StubbedContext```). +:::note Include +Notez l'utilisation d'```Include``` dans ```db.NounoursSet.Include(n => n.Carnet)``` sinon, les ```CarnetDeSante``` ne sont pas chargés. ```Include``` n'est pas utilisé ensuite dans ```db.Carnets``` car les ```Nounours``` ont déjà été chargés depuis la connexion. Mais on aurait pu faire les accès dans l'autre sens et dans ce cas d'abord ```db.Carnets.Include(c => c.Owner)``` puis simplement ```db.NounoursSet```. +::: +```csharp title='Program.cs' +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + WriteLine("Contenu de la base (nounours) : "); + foreach (var n in db.NounoursSet.Include(n => n.Carnet)) + { + WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}"); + } + + WriteLine("Contenu de la base (carnets de santé) : "); + foreach (var c in db.Carnets) + { + WriteLine($"\t{c}"); + } + + //... +} +``` +* La suite de l'exemple ajoute un nouveau ```Nounours``` et son ```CarnetDeSante``` puis affiche le contenu de la base de données. +```csharp title='Program.cs' +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + //... + + WriteLine("\nAjout d'un nounours et de son carnet de santé\n"); + + Nounours porg = new Nounours { Nom = "Porg", DateDeNaissance = new DateTime(2017, 7, 19), NbPoils = 123 }; + CarnetDeSante carnetPorg = new CarnetDeSante { LastModified = DateTime.Now, Owner = porg }; + porg.Carnet = carnetPorg; + + db.AddRange(porg, carnetPorg); + + db.SaveChanges(); +} +``` +```csharp title='Program.cs' +using (NounoursDBEntities db = new NounoursDBEntities()) +{ + WriteLine("Contenu de la base (nounours) : "); + foreach (var n in db.NounoursSet.Include(n => n.Carnet)) + { + WriteLine($"\t{n}, LastModified: {n.Carnet.LastModified.ToString("d")}"); + } + + WriteLine("Contenu de la base (carnets de santé) : "); + foreach (var c in db.Carnets) + { + WriteLine($"\t{c}"); + } +} +``` +## Comment exécuter cet exemple ? +Pour tester cette application, n'oubliez pas les commandes comme présentées dans l'exemple ex_041_001 : pour générer l'exemple, il vous faut d'abord préparer les migrations et les tables. + * Ouvrez la *Console du Gestionnaire de package* sous Windows ou le *Terminal* sous MacOSX, pour cela, dirigez-vous dans le menu *Outils*, puis *Gestionnaire de package NuGet*, puis *Console du Gestionnaire de package*. + * Dans la console que vous venez d'ouvrir, déplacez-vous dans le dossier du projet .NET Core, ici : +``` +cd .\p08_BDD_EntityFramework\ex_042_013_OneToOne_FluentAPI +``` +:::note Note + Si vous n'avez pas installé correctement EntityFrameworkCore, il vous faudra peut-être utiliser également : + +* ```dotnet tool install --global dotnet-ef``` si vous utilisez la dernière version de .NET Core (3.1 aujourd'hui), + +* ```dotnet tool install --global dotnet-ef --version 3.0.0``` si vous vous utiliser spécifiquement .NET Core 3.0. +::: + + * Migration : +``` +dotnet ef migrations add ex_042_013 --context StubbedContext +``` + * Création de la table : +``` +dotnet ef database update --context StubbedContext +``` + * Génération et exécution +Vous pouvez maintenant générer et exécuter l'exemple [**ex_042_013_OneToOne_FluentAPI ↗**](https://codefirst.iut.uca.fr/git/mchSamples_.NET/mchsamples-.net-core/src/branch/master/p08_BDD_EntityFramework/ex_042_013_OneToOne_FluentAPI). + + * Le résultat de l'exécution va ressembler à : +``` +Contenu de la base (nounours) : + 1: Chewbacca (27/05/1977, 1234567 poils), LastModified: 20/01/2020 + 2: Yoda (21/05/1980, 3 poils), LastModified: 21/05/1980 + 3: Ewok (25/05/1983, 3456789 poils), LastModified: 25/05/1983 +Contenu de la base (carnets de santé) : + 1 : carnet de Chewbacca, modifié la dernière fois le 20/01/2020 + 2 : carnet de Yoda, modifié la dernière fois le 21/05/1980 + 3 : carnet de Ewok, modifié la dernière fois le 25/05/1983 + +Ajout d'un nounours et de son carnet de santé + +Contenu de la base (nounours) : + 1: Chewbacca (27/05/1977, 1234567 poils), LastModified: 20/01/2020 + 2: Yoda (21/05/1980, 3 poils), LastModified: 21/05/1980 + 3: Ewok (25/05/1983, 3456789 poils), LastModified: 25/05/1983 + 4: Porg (19/07/2017, 123 poils), LastModified: 20/01/2020 +Contenu de la base (carnets de santé) : + 1 : carnet de Chewbacca, modifié la dernière fois le 20/01/2020 + 2 : carnet de Yoda, modifié la dernière fois le 21/05/1980 + 3 : carnet de Ewok, modifié la dernière fois le 25/05/1983 + 4 : carnet de Porg, modifié la dernière fois le 20/01/2020 +``` +:::note Note +L'identifiant du dernier ```Nounours``` sera vraisemblablement différent puisqu'il est créé par la base lors de l'insertion. +::: + +## Comment exécuter cet exemple sans le stub ? +Il suffit de faire exactement comme dans le paragraphe précédent, mais en choisissant le contexte ```NounoursDBEntities``` à la place de ```StubbedContext``` : +``` +dotnet ef migrations add ex_042_013 --context NounoursDBEntities +dotnet ef database update --context NounoursDBEntities +``` +Lors de l'exécution, le résultat sera évidemment différent puisqu'il n'y aura pas les 3 nounours du stub. + +## Comment vérifier quelles base et tables ont été créées et leur contenu ? +Pour vérifier le contenu de votre base SQLite, vous pouvez utiliser le programme *DB Browser* : +* Rendez-vous sur la page : https://sqlitebrowser.org/dl/ et téléchargez le programme *DB Browser*. +* Lancez *DB Browser for SQLite* +* Glissez-déposez au milieu de la fenêtre de *DB Browser for SQLite* le fichier *ex_042_013_OneToOne_FluentAPI.Nounours.db* qui a été généré par l'exécution du programme et qui se trouve près de *ex_042_013_OneToOne_FluentAPI.csproj*. +![DB Browser for SQLite](./02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser01.png) +* Choisissez ensuite l'onglet *Parcourir les données* +* Observez les résultats obtenus des deux tables +![DB Browser for SQLite](./02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser02.png) +![DB Browser for SQLite](./02_10_03_OneToOne_dataAnnotations/readme_files/dbbrowser03.png) + + diff --git a/Documentation/docusaurus/docs/Entity-Framework/intro.md b/Documentation/docusaurus/docs/Entity-Framework/intro.md index 6f60e17..0445a03 100644 --- a/Documentation/docusaurus/docs/Entity-Framework/intro.md +++ b/Documentation/docusaurus/docs/Entity-Framework/intro.md @@ -56,8 +56,8 @@ Ce chapitre s'attardera sur le lien entre le modèle et la base de données. En * [**2.10 : Relationships**](/docs/Entity-Framework/Model/Relationships/intro.md): où vous aurez plus de détails sur les relations entre entités * [**2.10.1. Single Property Navigation with naming conventions**](/docs/Entity-Framework/Model/Relationships/02_10_01_SinglePropertyNavigation_conventions/) : montre comment une relation d'association est traduite par *EF Core* lorsque cette association est unidirectionnelle entre deux entités, en utilisant les conventions d'écriture et/ou les annotations de données. * [**2.10.2. : Single Property navigation with Fluent API**](/docs/Entity-Framework/Model/Relationships/02_10_02_SinglePropertyNavigation_fluentAPI) : montre comment une relation d'association est traduite par *EF Core* lorsque cette association est unidirectionnelle entre deux entités, en utilisant la *FLuent API*. - * [**ex_042_012 : One To One with data annotations**](ex_042_012_OneToOne_conventions) : montre comment une relation d'association *One To One* est traduite par *EF Core* lorsque cette association est bidirectionnelle entre deux entités, en utilisant l'*annotation de données*. - * [**ex_042_013 : One To One with Fluent API**](ex_042_013_OneToOne_FluentAPI) : montre comment une relation d'association *One To One* est traduite par *EF Core* lorsque cette association est bidirectionnelle entre deux entités, en utilisant la *FluentAPI*. + * [**2.10.3. : One To One with data annotations**](/docs/Entity-Framework/Model/Relationships/02_10_03_OneToOne_dataAnnotations) : montre comment une relation d'association *One To One* est traduite par *EF Core* lorsque cette association est bidirectionnelle entre deux entités, en utilisant l'*annotation de données*. + * [**2.10.4. : One To One with Fluent API**](/docs/Entity-Framework/Model/Relationships/02_10_04_OneToOne_FluentAPI) : montre comment une relation d'association *One To One* est traduite par *EF Core* lorsque cette association est bidirectionnelle entre deux entités, en utilisant la *FluentAPI*. * [**ex_042_014 : One To Many with data annotations**](ex_042_014_OneToMany_dataAnnotations) : montre comment une relation d'association *One To Many* est traduite par *EF Core* en utilisant l'*annotation de données*. * [**ex_042_015 : One To Many with conventions**](ex_042_015_OneToMany_conventions) : montre comment une relation d'association *One To Many* est traduite par *EF Core* en utilisant les *conventions d'écriture*. * [**ex_042_016 : One To Many with Fluent API**](ex_042_016_OneToMany_FluentAPI) : montre comment une relation d'association *One To Many* est traduite par *EF Core* en utilisant la *Fluent API*.