added two more samples about OneToOne relationship
continuous-integration/drone/push Build is passing Details

master
Marc CHEVALDONNE 3 years ago
parent 9a72df475e
commit 9798f05b24

@ -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_). * ```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_). * ```Information``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_).
* ```Nounours``` possède une association vers ```Information``` * ```Nounours``` possède une association vers ```Information```
```csharp ```csharp title='Nounours.cs'
public Information Information public Information Information
{ {
get; set; get; set;
@ -64,7 +64,7 @@ public Information Information
* Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. * Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```.
* ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```. * ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```.
```csharp ```csharp title='NounoursDBEntities.cs'
public DbSet<Nounours> NounoursSet { get; set; } public DbSet<Nounours> 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*). * 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```. * ```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. 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```. 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) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@ -102,13 +102,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
} }
``` ```
Elle crée d'abord des entités d'```Information``` 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 info1 = new Information { InformationId = 1, MadeBy = "George Lucas", MadeIn = "Kashyyyk" };
var info2 = new Information { InformationId = 2, MadeBy = "George Lucas", MadeIn = "Dagobah" }; 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" }; var info3 = new Information { InformationId = 3, MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" };
``` ```
et les ajoute à la base : et les ajoute à la base :
```csharp ```csharp title='NounoursDBEntitiesWithStub.cs'
modelBuilder.Entity<Information>().HasData(info1, info2, info3); modelBuilder.Entity<Information>().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```. 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 : 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. 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é : 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<Nounours>().Property<int>("InformationId"); modelBuilder.Entity<Nounours>().Property<int>("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```. 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<Nounours>().HasData( modelBuilder.Entity<Nounours>().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("{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 }, 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<Nounours>().HasData(
### La classe ```Program``` ### 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. * ```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()) using (NounoursDBEntities db = new NounoursDBEntities())
{ {
WriteLine("Contenu de la base :"); 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```. 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``` : 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.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```. ```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. * 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.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(); db.SaveChangesAsync();
``` ```
```csharp ```csharp title='Program.cs'
using (NounoursDBEntities db = new NounoursDBEntities()) using (NounoursDBEntities db = new NounoursDBEntities())
{ {
WriteLine("Contenu de la base :"); WriteLine("Contenu de la base :");

@ -1,129 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="477.4189453125" height="122">
<defs />
<g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#C0C0C0" stroke="none" x="151" y="263" width="184.45556640625" height="102" opacity="0.2" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#ffffff" stroke="none" x="144" y="256" width="184.45556640625" height="102" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 144 256 L 328.45556640625 256 L 328.45556640625 358 L 144 358 L 144 256 Z Z" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 144 281 L 328.45556640625 281" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 144 349 L 328.45556640625 349" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="none" x="208.044189453125" y="275.5">
Nounours
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="298.5">
+UniqueId: Guid
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="313.5">
+Nom: string
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="328.5">
+DateDeNaissance: DateTime
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="149" y="343.5">
+NbPoils: int
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#C0C0C0" stroke="none" x="495" y="263" width="113.4189453125" height="87" opacity="0.2" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<rect fill="#ffffff" stroke="none" x="488" y="256" width="113.4189453125" height="87" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 488 256 L 601.4189453125 256 L 601.4189453125 343 L 488 343 L 488 256 Z Z" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 488 281 L 601.4189453125 281" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 488 334 L 601.4189453125 334" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="none" x="512.193603515625" y="275.5">
Information
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="493" y="298.5">
+InformationId: int
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="493" y="313.5">
+MadeBy: string
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="493" y="328.5">
+MadeIn: string
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 329 305 L 487 300" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<path fill="none" stroke="#000000" d="M 476.9755559235945 304.5288542655974 L 487 300 L 476.7092640537389 296.1140311781579" stroke-miterlimit="10" />
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="398.5" y="291.5">
+Information
</text>
</g>
</g>
<g transform="translate(-134,-246) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="461.5" y="319.5">
1
</text>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

@ -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_). * ```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_). * ```Information``` possède un identifiant unique (qui peut être utilisé par convention d'écriture ou _data annotation_).
* ```Nounours``` possède une association vers ```Information``` * ```Nounours``` possède une association vers ```Information```
```csharp ```csharp title='Nounours.cs'
public Information Information public Information Information
{ {
get; set; get; set;
@ -64,12 +64,12 @@ public Information Information
* Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```. * Comme dans les exemples précédents, ```NounoursDBEntities``` dérive de ```DbContext```.
* ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```. * ```NounoursDBEntities``` déclare un seul ```DbSet``` de ```Nounours```.
```csharp ```csharp title='NounoursDBEntities.cs'
public DbSet<Nounours> NounoursSet { get; set; } public DbSet<Nounours> 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*). * 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 : * 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) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId); modelBuilder.Entity<Nounours>().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. * 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<Nounours>().HasOne(n => n.Information); modelBuilder.Entity<Nounours>().HasOne(n => n.Information);
``` ```
##### Quelques explications supplémentaires : ##### 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```. * ```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. 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```. 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) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@ -120,13 +120,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
} }
``` ```
Elle crée d'abord des entités d'```Information``` 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 info1 = new Information { InformationId = 1, MadeBy = "George Lucas", MadeIn = "Kashyyyk" };
var info2 = new Information { InformationId = 2, MadeBy = "George Lucas", MadeIn = "Dagobah" }; 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" }; var info3 = new Information { InformationId = 3, MadeBy = "George Lucas", MadeIn = "Lune forestière d'Endor" };
``` ```
et les ajoute à la base : et les ajoute à la base :
```csharp ```csharp title='NounoursDBEntitiesWithStub.cs'
modelBuilder.Entity<Information>().HasData(info1, info2, info3); modelBuilder.Entity<Information>().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```. 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 : 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. 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é : 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<Nounours>().Property<int>("InformationId"); modelBuilder.Entity<Nounours>().Property<int>("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```. 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<Nounours>().HasData( modelBuilder.Entity<Nounours>().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("{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 }, 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<Nounours>().HasData(
### La classe ```Program``` ### 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. * ```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()) using (NounoursDBEntities db = new NounoursDBEntities())
{ {
WriteLine("Contenu de la base :"); 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```. 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``` : 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.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```. ```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. * 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.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(); db.SaveChangesAsync();
``` ```
```csharp ```csharp title='Program.cs'
using (NounoursDBEntities db = new NounoursDBEntities()) using (NounoursDBEntities db = new NounoursDBEntities())
{ {
WriteLine("Contenu de la base :"); WriteLine("Contenu de la base :");

@ -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 &sdot; Marc Chevaldonné*
[**&#9654; Browse Sample &#8599;**](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```.
<Mermaid chart={`
classDiagram
direction LR
class Nounours{
+UniqueId: int
+Nom: string
+DateDeNaissance: DateTime
+NbPoils: int
}
class CarnetDeSante{
+UniqueId: int
+LastModified: DateTime
}
Nounours "1" -- "1" CarnetDeSante : +Owner +Carnet
`}/>
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<Nounours> NounoursSet { get; set; }
public DbSet<CarnetDeSante> 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<CarnetDeSante>().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<Nounours>().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 &#8599;**](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)

@ -0,0 +1,134 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="533.39013671875" height="122">
<defs />
<g>
<g transform="translate(-78,-173) scale(1,1)">
<rect fill="#C0C0C0" stroke="none" x="95" y="190" width="184.45556640625" height="102" opacity="0.2" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<rect fill="#ffffff" stroke="none" x="88" y="183" width="184.45556640625" height="102" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 88 183 L 272.45556640625 183 L 272.45556640625 285 L 88 285 L 88 183 Z Z" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 88 208 L 272.45556640625 208" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 88 276 L 272.45556640625 276" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="none" x="152.044189453125" y="202.5">
Nounours
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="93" y="225.5">
+UniqueId: int
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="93" y="240.5">
+Nom: string
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="93" y="255.5">
+DateDeNaissance: DateTime
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="93" y="270.5">
+NbPoils: int
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<rect fill="#C0C0C0" stroke="none" x="455" y="205" width="153.39013671875" height="72" opacity="0.2" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<rect fill="#ffffff" stroke="none" x="448" y="198" width="153.39013671875" height="72" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 448 198 L 601.39013671875 198 L 601.39013671875 270 L 448 270 L 448 198 Z Z" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 448 223 L 601.39013671875 223" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 448 261 L 601.39013671875 261" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="bold" text-decoration="none" x="479.89013671875" y="217.5">
CarnetDeSante
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="453" y="240.5">
+UniqueId: int
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="453" y="255.5">
+LastModified: DateTime
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<path fill="none" stroke="#000000" d="M 273 234 L 447 234" stroke-miterlimit="10" />
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="275.5" y="225.5">
+Owner
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="290.5" y="252.5">
1
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="397.5" y="225.5">
+Carnet
</text>
</g>
</g>
<g transform="translate(-78,-173) scale(1,1)">
<g>
<path fill="none" stroke="none" />
<text fill="#000000" stroke="none" font-family="Arial" font-size="13px" font-style="normal" font-weight="normal" text-decoration="none" x="421.5" y="252.5">
1
</text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@ -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 &sdot; Marc Chevaldonné*
[**&#9654; Browse Sample &#8599;**](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```.
<Mermaid chart={`
classDiagram
direction LR
class Nounours{
+UniqueId: int
+Nom: string
+DateDeNaissance: DateTime
+NbPoils: int
}
class CarnetDeSante{
+UniqueId: int
+LastModified: DateTime
}
Nounours "1" -- "1" CarnetDeSante : +Owner +Carnet
`}/>
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<Nounours> NounoursSet { get; set; }
public DbSet<CarnetDeSante> 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<Nounours>().ToTable("TableNounours"); //nom de la table
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId); //définition de la clé primaire
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId)
.ValueGeneratedOnAdd(); //définition du mode de génération de la clé : génération à l'insertion
modelBuilder.Entity<Nounours>().Property(n => n.Nom).IsRequired()
.HasMaxLength(256); //définition de la colonne Nom
modelBuilder.Entity<Nounours>().Property(n => n.DateDeNaissance).HasColumnName("Naissance").HasColumnType("date"); //changement du nom de la colonne Naissance
//création de la table "Carnets"
modelBuilder.Entity<CarnetDeSante>().ToTable("Carnets"); // nom de la table
modelBuilder.Entity<CarnetDeSante>().HasKey(c => c.UniqueId); //définition de la clé primaire
modelBuilder.Entity<CarnetDeSante>().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<Nounours>() //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<CarnetDeSante>(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<Nounours>().ToTable("TableNounours");
```
* la définition de la clé primaire :
```csharp title='NounoursDBEntities.cs'
modelBuilder.Entity<Nounours>().HasKey(n => n.UniqueId);
modelBuilder.Entity<Nounours>().Property(n => n.UniqueId)
.ValueGeneratedOnAdd();
```
* définition de contraintes sur les colonnes de ```Nounours``` :
```csharp title='NounoursDBEntities.cs'
modelBuilder.Entity<Nounours>().Property(n => n.Nom)
.IsRequired()
.HasMaxLength(256);
modelBuilder.Entity<Nounours>().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<CarnetDeSante>().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<CarnetDeSante>().HasKey(c => c.UniqueId);
modelBuilder.Entity<CarnetDeSante>().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<CarnetDeSante>(c => c.UniqueId)```). Elle automatiquement reliée à la clé primaire de ```Nounours```.
```csharp title='NounoursDBEntities.cs'
modelBuilder.Entity<Nounours>() //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<CarnetDeSante>(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<CarnetDeSante>().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<Nounours>().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 &#8599;**](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)

@ -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 : 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.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*. * [**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*. * [**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*.
* [**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.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_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_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*. * [**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*.

Loading…
Cancel
Save