You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mchsamples-.net-core/p08_BDD_EntityFramework/ex_042_016_OneToMany_FluentAPI
Marc CHEVALDONNE a773557095
continuous-integration/drone/push Build is failing Details
update to .NET6
2 years ago
..
readme_files Update ex_042_016_classDiagram2.svg 5 years ago
Album.cs added sample ex_042_016; readme to be finished 5 years ago
AlbumDBEntities.cs added sample ex_042_016; readme to be finished 5 years ago
Morceau.cs added sample ex_042_016; readme to be finished 5 years ago
Program.cs added sample ex_042_016; readme to be finished 5 years ago
ReadMe.md Update ReadMe.md 4 years ago
StubbedContext.cs added sample ex_042_016; readme to be finished 5 years ago
ex_042_016_OneToMany_FluentAPI.csproj update to .NET6 2 years ago

ReadMe.md

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 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


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.

Ce qu'il faut noter :

  • Album possède une association vers Morceau
public ICollection<Morceau> Morceaux { get; set; } = new List<Morceau>();
  • Morceau possède une association vers Album
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) ou de manière implicite en utilisant les conventions d'écriture (cf. ex_042_015 : One To Many with 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, 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.
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).
//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).
//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é.
// Add the shadow property to the model
modelBuilder.Entity<Morceau>()
    .Property<int>("AlbumForeignKey");
  • 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

// 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.
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.
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.
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();
}
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
  • Choisissez ensuite l'onglet Parcourir les données
  • Observez les résultats obtenus des deux tables DB Browser for SQLite DB Browser for SQLite