🌐
🔍 100%
👁️

🎙️ Sélectionner une voix

🎙️ Select a voice

TP 5 : Persistance des Données (Entity Framework Core)

Module : Programmation .Net C#

Durée : 2h

Objectif : Dans le TP 4, notre SensorService stockait les données dans une simple List<SensorData> en mémoire (RAM). Au redémarrage, tout était perdu ! Ce TP remplace cette liste volatile par une vraie base de données SQLite grâce à Entity Framework Core (approche Code-First).

Pré-requis

📋 Rappel du TP 4

À la fin du TP 4, votre projet contenait :

⚠️ Problème : Les données disparaissent à chaque redémarrage ! Ce TP résout ce problème.

Activité 1 : Installation des Outils

Entity Framework Core n'est pas inclus par défaut. Il faut ajouter des "Nuget Packages".

1. Ouvrez le terminal intégré dans votre dossier de projet.

2. Exécutez les commandes suivantes pour installer le moteur SQLite et les outils de commande :

# Le moteur de base de données
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

# Les outils pour générer le code de migration
dotnet add package Microsoft.EntityFrameworkCore.Design

3. Vérifiez que l'outil de commande global dotnet-ef est installé :

dotnet tool install --global dotnet-ef

(Si c'est déjà installé, il vous le dira, c'est normal).

Activité 2 : Modélisation (Code-First)

Notre SensorData du TP 4 n'avait que Name et Value. Or, une base de données a besoin de :

Nous allons enrichir le modèle existant et ajouter de nouvelles entités.

Nous allons créer 3 entités liées :

Étape 1 : Créer Models/Location.cs

Un emplacement peut contenir plusieurs capteurs (relation 1-to-N).

using System.ComponentModel.DataAnnotations;

namespace DashboardData.Models;

public class Location
{
    [Key]
    public int Id { get; set; }

    [Required] [StringLength(100)]
    public string Name { get; set; }  // Ex: "Salle Serveur"

    public string? Building { get; set; } // Ex: "Bâtiment A"

    // Navigation : Un emplacement contient PLUSIEURS capteurs
    public ICollection<SensorData> Sensors { get; set; } = new List<SensorData>();
}

Étape 2 : Créer Models/Tag.cs

Un tag peut être sur plusieurs capteurs, et un capteur peut avoir plusieurs tags (relation N-to-N).

using System.ComponentModel.DataAnnotations;

namespace DashboardData.Models;

public class Tag
{
    [Key]
    public int Id { get; set; }

    [Required] [StringLength(30)]
    public string Label { get; set; } // Ex: "Critique", "Maintenance"

    // Navigation N-to-N : Un tag est sur PLUSIEURS capteurs
    public ICollection<SensorData> Sensors { get; set; } = new List<SensorData>();
}

Étape 3 : Modifier Models/SensorData.cs

Ajoutez les annotations, la clé étrangère vers Location, et la collection de Tags.

using System.ComponentModel.DataAnnotations;

namespace DashboardData.Models;

public class SensorData
{
    [Key] // Clé Primaire (PK)
    public int Id { get; set; }

    [Required] [StringLength(50)]
    public string Name { get; set; }

    public string Type { get; set; } = "Temperature";

    public double Value { get; set; }

    public DateTime LastUpdate { get; set; } = DateTime.Now;

    // ===== RELATIONS =====

    // FK vers Location (1-to-N) : chaque capteur est dans UN emplacement
    public int LocationId { get; set; }
    public Location Location { get; set; }

    // Navigation N-to-N : un capteur peut avoir PLUSIEURS tags
    public ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

💡 Comment EF Core détecte les relations ?

Convention de nommage : EF Core voit LocationId (int) + Location (objet) dans SensorData → il comprend automatiquement que c'est une Clé Étrangère vers la table Locations.

N-to-N automatique : EF Core voit ICollection<Tag> dans SensorData ET ICollection<SensorData> dans Tag → il génère automatiquement une table pivot (SensorDataTag).

Pas besoin de configuration supplémentaire !

Activité 3 : Le DbContext (Le Chef d'Orchestre)

Le DbContext est la classe qui fait le pont entre votre code C# et la base de données. C'est elle qui gère les connexions et les transactions.

1. Créez un nouveau dossier Data à la racine (s'il n'existe pas déjà pour les Services).

2. Créez le fichier Data/AppDbContext.cs.

using Microsoft.EntityFrameworkCore;
using DashboardData.Models;

namespace DashboardData.Data;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    // Chaque DbSet = une Table SQL
    public DbSet<SensorData> Sensors { get; set; }
    public DbSet<Location> Locations { get; set; }  // NOUVEAU
    public DbSet<Tag> Tags { get; set; }              // NOUVEAU
}

Activité 4 : Configuration & Injection

Il faut dire à l'application : "Utilise SQLite et stocke le fichier ici".

1. Ouvrez le fichier appsettings.json (à la racine). Ajoutez la chaîne de connexion :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=app.db"
  }
}

2. Ouvrez Program.cs. Ajoutez l'enregistrement du DbContext avant la ligne var app = builder.Build(); :

using Microsoft.EntityFrameworkCore;
using DashboardData.Data;

// ...

// Injection du DbContext avec SQLite
// On lit la chaîne de connexion depuis le fichier JSON
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(connectionString));

var app = builder.Build();

Activité 5 : Les Migrations (La Magie)

C'est le moment de vérité. Nous allons demander à EF Core de comparer nos classes C# avec la base de données (qui n'existe pas encore) et de générer le script SQL nécessaire.

1. Créer la migration : Dans le terminal, tapez :

dotnet ef migrations add InitialCreate

Regardez dans le dossier Migrations qui vient d'apparaître. Vous verrez du code C# qui décrit la création de la table.

2. Appliquer la migration (Créer la BDD) :

dotnet ef database update

Un fichier app.db est apparu à la racine de votre projet.

3. Vérification :

Exercices d'application (En autonomie)

Maintenant que le tuyau est posé, remplissons-le.

Exercice 1 : Initialisation de données avec Relations (Seeding)

Une base vide, c'est triste. Nous allons injecter des données avec des relations au démarrage.

1. Dans Program.cs, juste après var app = builder.Build();, ajoutez ce bloc :

// Création d'un scope temporaire pour récupérer les services
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<AppDbContext>();

    if (!context.Sensors.Any())
    {
        Console.WriteLine("--- Génération de données de test ---");

        // 1. Créer les Emplacements
        var labo = new Location { Name = "Labo", Building = "Bât. A" };
        var usine = new Location { Name = "Usine", Building = "Bât. B" };
        context.Locations.AddRange(labo, usine);

        // 2. Créer les Tags
        var tagCritique = new Tag { Label = "Critique" };
        var tagMaintenance = new Tag { Label = "Maintenance" };
        context.Tags.AddRange(tagCritique, tagMaintenance);
        context.SaveChanges(); // Sauvegarder AVANT pour générer les Id

        // 3. Créer les Capteurs avec relations
        var sonde1 = new SensorData
        {
            Name = "Sonde_Alpha", Value = 25.4,
            LocationId = labo.Id,
            Tags = new List<Tag> { tagCritique }
        };
        var sonde2 = new SensorData
        {
            Name = "Sonde_Beta", Value = 40.2,
            LocationId = usine.Id,
            Tags = new List<Tag> { tagCritique, tagMaintenance }
        };
        context.Sensors.AddRange(sonde1, sonde2);
        context.SaveChanges();
    }
}

2. Lancez l'application (dotnet watch).

3. Vérifiez dans SQLite Viewer : la table SensorDataTag doit contenir 3 lignes (les associations capteur-tag).

Exercice 2 : Ajouter un Historique de Valeurs (SensorValueHistory)

Scénario : Actuellement, un capteur stocke une seule Value. En réalité, un capteur enregistre un historique de mesures. Créez une entité SensorValueHistory liée à SensorData (relation 1-to-N).

💡 Indices :

Synthèse du TP 5

Vous avez mis en place une persistance de données professionnelle avec des relations.

À retenir :

  1. Code-First : On ne touche pas au SQL. On modifie les classes C#, et EF Core adapte la BDD.
  2. Relations :
    • 1-to-N : FK (LocationId) + objet de navigation (Location).
    • N-to-N : ICollection croisées → table pivot automatique.
  3. DbContext : C'est la session de travail avec la base.
  4. Migrations :
    • add : Calcule les changements (Git du schéma BDD).
    • update : Applique les changements au fichier .db.

Prochain TP (TP6) : Nous allons connecter notre Service (TP4) à ce DbContext (TP5) pour que l'interface graphique affiche et modifie les données de la base !

LAB 5: Data Persistence (Entity Framework Core)

Module: .Net C# Programming

Duration: 2h

Objective: In LAB 4, our SensorService stored data in a simple List<SensorData> in memory (RAM). On restart, everything was lost! This LAB replaces that volatile list with a real SQLite database using Entity Framework Core (Code-First approach).

Prerequisites

📋 LAB 4 Recap

At the end of LAB 4, your project contained:

⚠️ Problem: Data disappears on every restart! This LAB solves that.

Activity 1: Tool Installation

Entity Framework Core is not included by default. We need to add "Nuget Packages".

1. Open the integrated terminal in your project folder.

2. Run the following commands to install the SQLite engine and command tools:

# Database Engine
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

# Tools to generate migration code
dotnet add package Microsoft.EntityFrameworkCore.Design

3. Verify that the global command tool dotnet-ef is installed:

dotnet tool install --global dotnet-ef

(If it's already installed, it will tell you, which is normal).

Activity 2: Modeling (Code-First)

Our SensorData from LAB 4 only had Name and Value. But a database requires:

We will enrich the existing model and add new entities.

We will create 3 linked entities:

Step 1: Create Models/Location.cs

A location can contain multiple sensors (1-to-N relationship).

using System.ComponentModel.DataAnnotations;

namespace DashboardData.Models;

public class Location
{
    [Key]
    public int Id { get; set; }

    [Required] [StringLength(100)]
    public string Name { get; set; }  // E.g., "Server Room"

    public string? Building { get; set; } // E.g., "Building A"

    // Navigation: A location contains MULTIPLE sensors
    public ICollection<SensorData> Sensors { get; set; } = new List<SensorData>();
}

Step 2: Create Models/Tag.cs

A tag can be on multiple sensors, and a sensor can have multiple tags (N-to-N relationship).

using System.ComponentModel.DataAnnotations;

namespace DashboardData.Models;

public class Tag
{
    [Key]
    public int Id { get; set; }

    [Required] [StringLength(30)]
    public string Label { get; set; } // E.g., "Critical", "Maintenance"

    // Navigation N-to-N: A tag is on MULTIPLE sensors
    public ICollection<SensorData> Sensors { get; set; } = new List<SensorData>();
}

Step 3: Modify Models/SensorData.cs

Add annotations, the foreign key to Location, and the Tags collection.

using System.ComponentModel.DataAnnotations;

namespace DashboardData.Models;

public class SensorData
{
    [Key] // Primary Key (PK)
    public int Id { get; set; }

    [Required] [StringLength(50)]
    public string Name { get; set; }

    public string Type { get; set; } = "Temperature";

    public double Value { get; set; }

    public DateTime LastUpdate { get; set; } = DateTime.Now;

    // ===== RELATIONSHIPS =====

    // FK to Location (1-to-N): each sensor is in ONE location
    public int LocationId { get; set; }
    public Location Location { get; set; }

    // Navigation N-to-N: a sensor can have MULTIPLE tags
    public ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

💡 How does EF Core detect relationships?

Naming Convention: EF Core sees LocationId (int) + Location (object) in SensorData → it automatically understands it's a Foreign Key to the Locations table.

Automatic N-to-N: EF Core sees ICollection<Tag> in SensorData AND ICollection<SensorData> in Tag → it automatically creates a pivot table (SensorDataTag).

No extra configuration needed!

Activity 3: The DbContext (The Orchestrator)

The DbContext is the class that bridges your C# code and the database. It manages connections and transactions.

1. Create a new Data folder at the root (if it doesn't exist for Services).

2. Create the file Data/AppDbContext.cs.

using Microsoft.EntityFrameworkCore;
using DashboardData.Models;

namespace DashboardData.Data;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    // Each DbSet = a SQL Table
    public DbSet<SensorData> Sensors { get; set; }
    public DbSet<Location> Locations { get; set; }  // NEW
    public DbSet<Tag> Tags { get; set; }              // NEW
}

Activity 4: Configuration & Injection

We must tell the app: "Use SQLite and store the file here".

1. Open appsettings.json (at the root). Add the connection string:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=app.db"
  }
}

2. Open Program.cs. Add the DbContext registration before the line var app = builder.Build();:

using Microsoft.EntityFrameworkCore;
using DashboardData.Data;

// ...

// Inject DbContext with SQLite
// Connect string is read from JSON file
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(connectionString));

var app = builder.Build();

Activity 5: Migrations (The Magic)

Moment of truth. We will ask EF Core to compare our C# classes with the database (which doesn't exist yet) and generate the necessary SQL script.

1. Create the migration: In the terminal, type:

dotnet ef migrations add InitialCreate

Look in the Migrations folder that just appeared. You will see C# code describing the table creation.

2. Apply the migration (Create DB):

dotnet ef database update

An app.db file appeared at the root of your project.

3. Verification:

Application Exercises (Independent)

Now that the pipe is laid, let's fill it up.

Exercise 1: Data Seeding with Relationships

An empty database is sad. We will inject data with relationships at startup.

1. In Program.cs, right after var app = builder.Build();, add this block:

// Create temporary scope to retrieve services
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<AppDbContext>();

    if (!context.Sensors.Any())
    {
        Console.WriteLine("--- Generating Test Data ---");

        // 1. Create Locations
        var lab = new Location { Name = "Lab", Building = "Bldg. A" };
        var factory = new Location { Name = "Factory", Building = "Bldg. B" };
        context.Locations.AddRange(lab, factory);

        // 2. Create Tags
        var tagCritical = new Tag { Label = "Critical" };
        var tagMaintenance = new Tag { Label = "Maintenance" };
        context.Tags.AddRange(tagCritical, tagMaintenance);
        context.SaveChanges(); // Save BEFORE to generate Ids

        // 3. Create Sensors with relationships
        var sensor1 = new SensorData
        {
            Name = "Sensor_Alpha", Value = 25.4,
            LocationId = lab.Id,
            Tags = new List<Tag> { tagCritical }
        };
        var sensor2 = new SensorData
        {
            Name = "Sensor_Beta", Value = 40.2,
            LocationId = factory.Id,
            Tags = new List<Tag> { tagCritical, tagMaintenance }
        };
        context.Sensors.AddRange(sensor1, sensor2);
        context.SaveChanges();
    }
}

2. Run the application (dotnet watch).

3. Verify in SQLite Viewer: the SensorDataTag table should contain 3 rows (sensor-tag associations).

Exercise 2: Add a Value History (SensorValueHistory)

Scenario: Currently, a sensor stores a single Value. In reality, a sensor records a measurement history. Create a SensorValueHistory entity linked to SensorData (1-to-N relationship).

💡 Hints:

LAB 5 Synthesis

You have set up professional data persistence with relationships.

Key Takeaways:

  1. Code-First: We don't touch SQL. We modify C# classes, and EF Core adapts the DB.
  2. Relationships:
    • 1-to-N: FK (LocationId) + navigation object (Location).
    • N-to-N: Cross ICollections → automatic pivot table.
  3. DbContext: It's the work session with the database.
  4. Migrations:
    • add: Calculates changes (Git of DB schema).
    • update: Applies changes to the .db file.

Next LAB (LAB 6): We will connect our Service (LAB 4) to this DbContext (LAB 5) so the GUI displays and modifies the DB data!