|
|
|
# ex_042_016_OneToMany_FluentAPI
|
|
|
|
|
|
|
|
*25/01/2020 ⋅ Marc Chevaldonné*
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
Cet exemple montre comment réaliser une relation *One To Many* entre deux entités
|
|
|
|
avec *Entity Framework Core* et la *Fluent API*.
|
|
|
|
Une version équivalente réalisée avec les *annotations de données* et
|
|
|
|
l'établissement d'une clé étrangère de manière explicite est disponible dans
|
|
|
|
l'exemple
|
|
|
|
[**ex_042_014 : One To Many with data annotations**](../ex_042_015_OneToMany_dataAnnotations)
|
|
|
|
Une version équivalente 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 conventions**](../ex_042_015_OneToMany_conventions)
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 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="../ex_042_014_OneToMany_dataAnnotations/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 sera la clé
|
|
|
|
* primaire et sera généré par la base lors de l'insertion, cf. ```AlbumDBEntities```).
|
|
|
|
* ```Morceau``` possède un identifiant unique ```MorceauId``` (qui sera la clé
|
|
|
|
* primaire et sera généré par la base lors de l'insertion, cf. ```AlbumDBEntities```).
|
|
|
|
* 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 de la *Fluent API*, ceci peut se faire dans la classe
|
|
|
|
qui dérive de ```DbContext```. Mais il est possible de le faire de manière explicite
|
|
|
|
en utilisant les *annotations de données* (cf. [ex_042_014 : One To Many with data annotations](../ex_042_014_OneToMany_dataAnnotations))
|
|
|
|
ou de manière implicite en utilisant les *conventions d'écriture*
|
|
|
|
(cf. [ex_042_015 : One To Many with conventions](../ex_042_016_OneToMany_conventions)).
|
|
|
|
Ici, on __N'__ ajoute __PAS__ de nouvelle propriété à ```Morceau``` pour stocker
|
|
|
|
l'identifiant unique de ```Album``` comme dans l'exemple [ex_042_014 : One To Many with data annotations](../ex_042_014_OneToMany_dataAnnotations),
|
|
|
|
mais on pourrait le faire (cf. commentaires plus bas).
|
|
|
|
|
|
|
|
### 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; }
|
|
|
|
```
|
|
|
|
Dans la méthode ```OnModelCreating``` :
|
|
|
|
* on définit la clé primaire de ```Album``` (ceci n'est pas obligatoire, puisqu'on
|
|
|
|
ne fait que refaire ce que font déjà les convetions d'écriture).
|
|
|
|
```csharp
|
|
|
|
//création de la table Album
|
|
|
|
modelBuilder.Entity<Album>().HasKey(a => a.AlbumId); //définition de la clé primaire
|
|
|
|
modelBuilder.Entity<Album>().Property(a => a.AlbumId)
|
|
|
|
.ValueGeneratedOnAdd(); //définition du mode de génération de la clé : génération à l'insertion
|
|
|
|
```
|
|
|
|
* on définit la clé primaire de ```Morceau``` (ceci n'est pas obligatoire, puisqu'on
|
|
|
|
ne fait que refaire ce que font déjà les convetions d'écriture).
|
|
|
|
```csharp
|
|
|
|
//création de la table Morceau
|
|
|
|
modelBuilder.Entity<Morceau>().HasKey(m => m.MorceauId); //définition de la clé primaire
|
|
|
|
modelBuilder.Entity<Morceau>().Property(m => m.MorceauId)
|
|
|
|
.ValueGeneratedOnAdd(); //définition du mode de génération de la clé : génération à l'insertion
|
|
|
|
```
|
|
|
|
* on ajoute la clé étrangère dans l'entité ```Morceau``` qui permettra d'établir la
|
|
|
|
relation OnToMany avec l'```Album``` concerné. 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é.
|
|
|
|
```csharp
|
|
|
|
// Add the shadow property to the model
|
|
|
|
modelBuilder.Entity<Morceau>()
|
|
|
|
.Property<int>("AlbumForeignKey");
|
|
|
|
```
|
|
|
|
<img src="./readme_files/ex_042_016_classDiagram2.svg"/>
|
|
|
|
|
|
|
|
* on décrit ensuite la relation *OneToMany* avec les lignes suivantes, qu'on
|
|
|
|
peut interpréter de la manière suivante :
|
|
|
|
|
|
|
|
> 1 ```Morceau``` est lié à 1 ```Album``` qui lui-même est lié à
|
|
|
|
PLUSIEURS ```Morceau``` en utilisant la clé étrangère ```HasForeignKey```
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
// Use the shadow property as a foreign key
|
|
|
|
modelBuilder.Entity<Morceau>()
|
|
|
|
.HasOne(m => m.Album)
|
|
|
|
.WithMany(a => a.Morceaux)
|
|
|
|
.HasForeignKey("AlbumForeignKey");
|
|
|
|
```
|
|
|
|
* notez enfin que la clé étrangère est ici également une *shadow property*
|
|
|
|
puisqu'elle est définie dans ```OnModelCreating``` et pas directement dans la
|
|
|
|
classe entité (ici ```Morceau```).
|
|
|
|
|
|
|
|
### 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 { MorceauId = 1, AlbumForeignKey = 1, Titre = "So What" },
|
|
|
|
new { MorceauId = 2, AlbumForeignKey = 1, Titre = "Freddie Freeloader" },
|
|
|
|
new { MorceauId = 3, AlbumForeignKey = 1, Titre = "Blue in Green" },
|
|
|
|
new { MorceauId = 4, AlbumForeignKey = 1, Titre = "All Blues" },
|
|
|
|
new { MorceauId = 5, AlbumForeignKey = 1, Titre = "Flamenco Sketches" },
|
|
|
|
new { MorceauId = 6, AlbumForeignKey = 2, Titre = "Catta" },
|
|
|
|
new { MorceauId = 7, AlbumForeignKey = 2, Titre = "Idle While" },
|
|
|
|
new { MorceauId = 8, AlbumForeignKey = 2, Titre = "Les Noirs Marchant" },
|
|
|
|
new { MorceauId = 9, AlbumForeignKey = 2, Titre = "Dialogue" },
|
|
|
|
new { MorceauId = 10, AlbumForeignKey = 2, Titre = "Ghetto Lights" },
|
|
|
|
new { MorceauId = 11, AlbumForeignKey = 2, Titre = "Jasper" }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
* Contrairement aux deux exemples précédents, il n'est pas nécessaire de préciser
|
|
|
|
qu'elle est la clé étrangère pour relier les tables puisque cela a été fait
|
|
|
|
dans la classe ```AlbumDBEntities```
|
|
|
|
* 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```
|
|
|
|
ajoutée précédemment) 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 (pour cela,
|
|
|
|
dirigez-vous dans le menu *Outils*, puis *Gestionnaire de package NuGet*, puis
|
|
|
|
*Console du Gestionnaire de package*) ou le *Terminal* sous MacOSX.
|
|
|
|
* 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_016_OneToMany_FluentAPI
|
|
|
|
```
|
|
|
|
*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 : vous devez préciser la classe fille de ```DbContext``` à utiliser :
|
|
|
|
soit ```AlbumDBEntities```, soit ```StubbedContext```.
|
|
|
|
```
|
|
|
|
dotnet ef migrations add ex_042_016 --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_016_OneToMany_FluentAPI**.
|
|
|
|
|
|
|
|
* Le résultat de l'exécution va ressembler à (si vous avez utilisé
|
|
|
|
```StubbedContext```) :
|
|
|
|
```
|
|
|
|
Albums :
|
|
|
|
1: Kind of Blue (sorti le : 17/08/1959)
|
|
|
|
So What
|
|
|
|
Freddie Freeloader
|
|
|
|
Blue in Green
|
|
|
|
All Blues
|
|
|
|
Flamenco Sketches
|
|
|
|
2: Dialogue (sorti le : 01/09/1965)
|
|
|
|
Catta
|
|
|
|
Idle While
|
|
|
|
Les Noirs Marchant
|
|
|
|
Dialogue
|
|
|
|
Ghetto Lights
|
|
|
|
Jasper
|
|
|
|
|
|
|
|
Morceaux :
|
|
|
|
1: So What (album : Kind of Blue)
|
|
|
|
2: Freddie Freeloader (album : Kind of Blue)
|
|
|
|
3: Blue in Green (album : Kind of Blue)
|
|
|
|
4: All Blues (album : Kind of Blue)
|
|
|
|
5: Flamenco Sketches (album : Kind of Blue)
|
|
|
|
6: Catta (album : Dialogue)
|
|
|
|
7: Idle While (album : Dialogue)
|
|
|
|
8: Les Noirs Marchant (album : Dialogue)
|
|
|
|
9: Dialogue (album : Dialogue)
|
|
|
|
10: Ghetto Lights (album : Dialogue)
|
|
|
|
11: Jasper (album : Dialogue)
|
|
|
|
|
|
|
|
Ajout d'un album et 6 morceaux...
|
|
|
|
|
|
|
|
Albums :
|
|
|
|
1: Kind of Blue (sorti le : 17/08/1959)
|
|
|
|
So What
|
|
|
|
Freddie Freeloader
|
|
|
|
Blue in Green
|
|
|
|
All Blues
|
|
|
|
Flamenco Sketches
|
|
|
|
2: Dialogue (sorti le : 01/09/1965)
|
|
|
|
Catta
|
|
|
|
Idle While
|
|
|
|
Les Noirs Marchant
|
|
|
|
Dialogue
|
|
|
|
Ghetto Lights
|
|
|
|
Jasper
|
|
|
|
3: Captain Marvel (sorti le : 03/03/1972)
|
|
|
|
La Fiesta
|
|
|
|
Five Hundred Miles High
|
|
|
|
Captain Marvel
|
|
|
|
Time's Lie
|
|
|
|
Lush Life
|
|
|
|
Day Waves
|
|
|
|
|
|
|
|
Morceaux :
|
|
|
|
1: So What (album : Kind of Blue)
|
|
|
|
2: Freddie Freeloader (album : Kind of Blue)
|
|
|
|
3: Blue in Green (album : Kind of Blue)
|
|
|
|
4: All Blues (album : Kind of Blue)
|
|
|
|
5: Flamenco Sketches (album : Kind of Blue)
|
|
|
|
6: Catta (album : Dialogue)
|
|
|
|
7: Idle While (album : Dialogue)
|
|
|
|
8: Les Noirs Marchant (album : Dialogue)
|
|
|
|
9: Dialogue (album : Dialogue)
|
|
|
|
10: Ghetto Lights (album : Dialogue)
|
|
|
|
11: Jasper (album : Dialogue)
|
|
|
|
12: La Fiesta (album : Captain Marvel)
|
|
|
|
13: Five Hundred Miles High (album : Captain Marvel)
|
|
|
|
14: Captain Marvel (album : Captain Marvel)
|
|
|
|
15: Time's Lie (album : Captain Marvel)
|
|
|
|
16: Lush Life (album : Captain Marvel)
|
|
|
|
17: Day Waves (album : Captain Marvel)
|
|
|
|
```
|
|
|
|
|
|
|
|
## 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 ```AlbumDBEntities``` à la place de ```StubbedContext``` :
|
|
|
|
```
|
|
|
|
dotnet ef migrations add ex_042_016 --context AlbumDBEntities
|
|
|
|
dotnet ef database update --context AlbumDBEntities
|
|
|
|
```
|
|
|
|
Lors de l'exécution, le résultat sera évidemment différent puisqu'il n'y aura pas les
|
|
|
|
2 albums du stub.
|
|
|
|
Il pourra ressembler à :
|
|
|
|
```
|
|
|
|
Albums :
|
|
|
|
|
|
|
|
Morceaux :
|
|
|
|
|
|
|
|
Ajout d'un album et 6 morceaux...
|
|
|
|
|
|
|
|
Albums :
|
|
|
|
1: Captain Marvel (sorti le : 03/03/1972)
|
|
|
|
La Fiesta
|
|
|
|
Five Hundred Miles High
|
|
|
|
Captain Marvel
|
|
|
|
Time's Lie
|
|
|
|
Lush Life
|
|
|
|
Day Waves
|
|
|
|
|
|
|
|
Morceaux :
|
|
|
|
1: La Fiesta (album : Captain Marvel)
|
|
|
|
2: Five Hundred Miles High (album : Captain Marvel)
|
|
|
|
3: Captain Marvel (album : Captain Marvel)
|
|
|
|
4: Time's Lie (album : Captain Marvel)
|
|
|
|
5: Lush Life (album : Captain Marvel)
|
|
|
|
6: Day Waves (album : Captain Marvel)
|
|
|
|
```
|
|
|
|
|
|
|
|
## 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_016_OneToMany_FluentAPI.Albums.db* qui a été généré par l'exécution du
|
|
|
|
programme et qui se trouve près de *ex_042_016_OneToMany_FluentAPI.csproj*.
|
|
|
|
![DB Browser for SQLite](../ex_042_015_OneToMany_conventions/readme_files/dbbrowser01.png)
|
|
|
|
* Choisissez ensuite l'onglet *Parcourir les données*
|
|
|
|
* Observez les résultats obtenus des deux tables
|
|
|
|
![DB Browser for SQLite](../ex_042_015_OneToMany_conventions/readme_files/dbbrowser02.png)
|
|
|
|
![DB Browser for SQLite](../ex_042_015_OneToMany_conventions/readme_files/dbbrowser03.png)
|
|
|
|
|
|
|
|
|