parent
35fe2b6455
commit
17b2858212
@ -0,0 +1,257 @@
|
||||
# ex_042_014_OneToMany_dataAnnotations
|
||||
|
||||
*21/01/2020 ⋅ Marc Chevaldonné*
|
||||
|
||||
---
|
||||
|
||||
Cet exemple montre comment réaliser une relation *One To Many* entre deux entités avec *Entity Framework Core* et l'*annotation de données*.
|
||||
Une version équivalent réalisée avec les *conventions d'écriture* et l'établissement d'une clé étrangère de manière implicite est disponible dans l'exemple
|
||||
[**ex_042_015 : One To Many with convenions**](../ex_042_015_OneToMany_conventions)
|
||||
Une version équivalente réalisée avec la *Fluent API* est disponible dans l'exemple
|
||||
[**ex_042_016 : One To Many with Fluent API**](../ex_042_016_OneToMany_FluentAPI)
|
||||
|
||||
---
|
||||
|
||||
## Comment est construit cet exemple ?
|
||||
* Le projet est de type .NET Core
|
||||
* Il contient quatre classes :
|
||||
* ```Album```
|
||||
* ```Morceau```
|
||||
* ```AlbumDBEntities```
|
||||
* ```StubbedContext```
|
||||
|
||||
### Les classes entités : ```Album``` et ```Morceau```
|
||||
|
||||
La relation **One To Many** est mise en évidence de la manière suivante : un ```Album``` possède plusieurs ```Morceau```,
|
||||
et un ```Morceau``` possède un ```Album```.
|
||||
Un ```Album``` contient différentes propriétés (```Titre```, ```DateDeSortie```) dont ```Morceaux``` de type ```ICollection<Morceau>```.
|
||||
Un ```Morceau``` possède une propriété de type ```string``` (```Titre```), et une propriété ```Album``` de type ```Album```.
|
||||
<img src="./readme_files/ex_042_014_classDiagram.svg"/>
|
||||
|
||||
Ce qu'il faut noter :
|
||||
* ```Album``` possède une association vers ```Morceau```
|
||||
```csharp
|
||||
public ICollection<Morceau> Morceaux { get; set; } = new List<Morceau>();
|
||||
```
|
||||
* ```Morceau``` possède une association vers ```Album```
|
||||
```csharp
|
||||
public Album Album { get; set; }
|
||||
```
|
||||
* ```Album``` possède un identifiant unique ```AlbumId``` (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)]```.
|
||||
* ```Morceau``` possède un identifiant unique ```MorceauId``` (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)]```.
|
||||
* Le lien entre les deux tables va se faire avec l'ajout d'une colonne dans la table de ```Morceau``` qui permettra de stocker l'```Album``` auquel ce ```Morceau``` est lié.
|
||||
Dans le cas de l'utilisation des *conventions d'écriture* et des *annotations de données*, ceci peut se faire explicitement ou implicitement. Cet exemple, le fait de manière explicite avec les *annotations de données*. Mais il est possible
|
||||
de le faire de manière explicite en utilisant les *conventions d'écriture* (cf. [ex_042_015 : One To Many with convenions](../ex_042_015_OneToMany_conventions))
|
||||
ou de manière explicite en utilisant la *Fluent API* (cf. [ex_042_016 : One To Many with Fluent API](../ex_042_016_OneToMany_FluentAPI)). Toutefois, pour l'utilisation de données *stubbées*, il est nécessaire d'expliciter cette propriété (cf. plus bas, lors de l'utilisation de ```StubbedContext```).
|
||||
Ici, on ajoute donc une nouvelle propriété à ```Morceau``` qui va permettre de stocker l'identifiant unique de ```Album``` (cette propriété doit donc être du même type que la clé primaire de ```Albuml```) :
|
||||
```csharp
|
||||
public int AlbumForeignKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
puis on annote la propriété ```Album``` pour préciser que cette relation se base sur la clé étrangère définie plus haut :
|
||||
```csharp
|
||||
[ForeignKey("AlbumForeignKey")]
|
||||
public Album Album
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
```
|
||||
|
||||
### La classe ```AlbumDBEntities```
|
||||
|
||||
* Comme dans les exemples précédents, ```AlbumDBEntities``` dérive de ```DbContext```.
|
||||
* ```AlbumDBEntities``` déclare deux ```DbSet``` : un de ```Album``` et l'autre de ```Morceau```.
|
||||
```csharp
|
||||
public DbSet<Album> Albums { get; set; }
|
||||
public DbSet<Morceau> Morceaux { get; set; }
|
||||
```
|
||||
##### Quelques explications supplémentaires :
|
||||
Comme dit plus haut, le lien entre les deux tables va se faire avec l'ajout d'une colonne dans la table de ```Morceau``` qui permettra de stocker l'```Album``` auquel ce ```Morceau``` est lié.
|
||||
Dans le cas de l'utilisation des *conventions d'écriture* et des *annotations de données*, ceci peut se faire explicitement ou implicitement. Cet exemple, le fait de manière explicite avec les *annotations de données*. Mais il est possible
|
||||
de le faire de manière explicite en utilisant les *conventions d'écriture* (cf. [ex_042_015 : One To Many with convenions](../ex_042_015_OneToMany_conventions))
|
||||
ou de manière explicite en utilisant la *Fluent API* (cf. [ex_042_016 : One To Many with Fluent API](../ex_042_016_OneToMany_FluentAPI)). Toutefois, pour l'utilisation de données *stubbées*, il est nécessaire d'expliciter cette propriété (cf. plus bas, lors de l'utilisation de ```StubbedContext```).
|
||||
|
||||
### La classe ```StubbedContext```
|
||||
|
||||
* ```StubbedContext``` est une classe fille de ```AlbumDBEntities```.
|
||||
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
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
Album kindofblue = new Album { AlbumId=1, Titre = "Kind of Blue", DateDeSortie = new DateTime(1959, 8, 17) };
|
||||
Album dialogue = new Album { AlbumId=2, Titre = "Dialogue", DateDeSortie = new DateTime(1965, 9, 1) };
|
||||
|
||||
modelBuilder.Entity<Album>().HasData(kindofblue, dialogue);
|
||||
|
||||
modelBuilder.Entity<Morceau>().HasData(new Morceau { MorceauId = 1, AlbumForeignKey = 1, Titre = "So What" },
|
||||
new Morceau { MorceauId = 2, AlbumForeignKey = 1, Titre = "Freddie Freeloader" },
|
||||
new Morceau { MorceauId = 3, AlbumForeignKey = 1, Titre = "Blue in Green" },
|
||||
new Morceau { MorceauId = 4, AlbumForeignKey = 1, Titre = "All Blues" },
|
||||
new Morceau { MorceauId = 5, AlbumForeignKey = 1, Titre = "Flamenco Sketches" },
|
||||
new Morceau { MorceauId = 6, AlbumForeignKey = 2, Titre = "Catta" },
|
||||
new Morceau { MorceauId = 7, AlbumForeignKey = 2, Titre = "Idle While" },
|
||||
new Morceau { MorceauId = 8, AlbumForeignKey = 2, Titre = "Les Noirs Marchant" },
|
||||
new Morceau { MorceauId = 9, AlbumForeignKey = 2, Titre = "Dialogue" },
|
||||
new Morceau { MorceauId = 10, AlbumForeignKey = 2, Titre = "Ghetto Lights" },
|
||||
new Morceau { MorceauId = 11, AlbumForeignKey = 2, Titre = "Jasper" } );
|
||||
}
|
||||
```
|
||||
* Remarquez que __À AUCUN MOMENT__ nous ne précisons les valeurs des propriétés ```Album``` de ```Morceau``` et ```Morceaux``` de ```Album```.
|
||||
Le simple fait d'utiliser la clé étrangère (propriété ```AlbumForeignKey```) dans ```Morceau``` est suffisant.
|
||||
* 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 ```Morceaux``` et ```Album```.
|
||||
|
||||
### La classe ```Program```
|
||||
* 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 ```AlbumDBEntites```) ou le stub (si la migration est faite à partir de ```StubbedContext```).
|
||||
*Notez l'utilisation d'```Include``` dans ```db.Albums.Include(a => a.Morceaux)``` sinon, les ```Morceau``` ne sont pas chargés.
|
||||
```Include``` n'est pas utilisé ensuite dans ```db.Morceaux``` car les ```Album``` 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.Morceaux.Include(m => m.Album)``` puis simplement ```db.Albums```.*
|
||||
```csharp
|
||||
using (AlbumDBEntities db = new AlbumDBEntities())
|
||||
{
|
||||
WriteLine("Albums : ");
|
||||
foreach (var a in db.Albums.Include(a => a.Morceaux))
|
||||
{
|
||||
WriteLine($"\t{a.AlbumId}: {a.Titre} (sorti le : {a.DateDeSortie.ToString("d")})");
|
||||
foreach (var m in a.Morceaux)
|
||||
{
|
||||
WriteLine($"\t\t{m.Titre}");
|
||||
}
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
|
||||
WriteLine("Morceaux :");
|
||||
foreach (var m in db.Morceaux)
|
||||
{
|
||||
WriteLine($"\t{m.MorceauId}: {m.Titre} (album : {m.Album.Titre})");
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
* La suite de l'exemple ajoute un nouvel ```Album``` et ses ```Morceau``` puis affiche le contenu de la base de données.
|
||||
```csharp
|
||||
using (AlbumDBEntities db = new AlbumDBEntities())
|
||||
{
|
||||
//...
|
||||
|
||||
WriteLine("\nAjout d'un album et 6 morceaux...\n");
|
||||
|
||||
Album captainMarvel = new Album { Titre = "Captain Marvel", DateDeSortie = new DateTime(1972, 3, 3) };
|
||||
Morceau[] morceaux = { new Morceau { Titre = "La Fiesta", Album = captainMarvel },
|
||||
new Morceau { Titre = "Five Hundred Miles High", Album = captainMarvel },
|
||||
new Morceau { Titre = "Captain Marvel", Album = captainMarvel },
|
||||
new Morceau { Titre = "Time's Lie", Album = captainMarvel },
|
||||
new Morceau { Titre = "Lush Life", Album = captainMarvel },
|
||||
new Morceau { Titre = "Day Waves", Album = captainMarvel }
|
||||
};
|
||||
foreach (var m in morceaux)
|
||||
{
|
||||
captainMarvel.Morceaux.Add(m);
|
||||
}
|
||||
|
||||
db.Add(captainMarvel);
|
||||
db.SaveChanges();
|
||||
}
|
||||
```
|
||||
```csharp
|
||||
using (AlbumDBEntities db = new AlbumDBEntities())
|
||||
{
|
||||
WriteLine("Albums : ");
|
||||
foreach (var a in db.Albums.Include(a => a.Morceaux))
|
||||
{
|
||||
WriteLine($"\t{a.AlbumId}: {a.Titre} (sorti le : {a.DateDeSortie.ToString("d")})");
|
||||
foreach (var m in a.Morceaux)
|
||||
{
|
||||
WriteLine($"\t\t{m.Titre}");
|
||||
}
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
|
||||
WriteLine("Morceaux :");
|
||||
foreach (var m in db.Morceaux)
|
||||
{
|
||||
WriteLine($"\t{m.MorceauId}: {m.Titre} (album : {m.Album.Titre})");
|
||||
}
|
||||
}
|
||||
```
|
||||
## 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*:
|
||||
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**.
|
||||
|
||||
* 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 : 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,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SQLite" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 9.0 KiB |
Loading…
Reference in new issue