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).
DashboardData fonctionnel avec le SensorService et l'injection de
dépendances (TP 4).À la fin du TP 4, votre projet contenait :
Models/SensorData.cs : une classe simple avec Name et Value.
Services/SensorService.cs : un service stockant les capteurs dans une
List<SensorData> en mémoire.
Services/ISensorService.cs : l'interface du service.Program.cs : l'enregistrement DI
(AddScoped<ISensorService, SensorService>).⚠️ Problème : Les données disparaissent à chaque redémarrage ! Ce TP résout ce problème.
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).
Notre SensorData du TP 4 n'avait que Name et Value. Or, une base de
données a besoin de :
[Key]) pour identifier chaque enregistrement.
[Required], [StringLength]) pour les
contraintes SQL.Nous allons enrichir le modèle existant et ajouter de nouvelles entités.
Nous allons créer 3 entités liées :
Location : l'emplacement physique du capteur (relation 1-to-N).SensorData : le capteur lui-même.Tag : des étiquettes/catégories (relation N-to-N).Models/Location.csUn emplacement peut contenir plusieurs capteurs (relation 1-to-N).
using System.ComponentModel.DataAnnotations;
namespace DashboardData.Models;
public class Location
{
public int Id { get; set; }
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>();
}
Models/Tag.csUn 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
{
public int Id { get; set; }
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>();
}
Models/SensorData.csAjoutez les annotations, la clé étrangère vers Location, et la
collection de Tags.
using System.ComponentModel.DataAnnotations;
namespace DashboardData.Models;
public class SensorData
{
// Clé Primaire (PK)
public int Id { get; set; }
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>();
}
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 !
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
}
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();
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 :
app.db pour voir 4
tables :
Sensors, Locations, Tags, et SensorDataTag (table
pivot générée automatiquement).
Maintenant que le tuyau est posé, remplissons-le.
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).
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 :
SensorValueHistory sont :
Id (PK), Value (double), Timestamp (DateTime),
SensorDataId (FK), SensorData (navigation).
SensorDataId, EF Core
comprend automatiquement que c'est la FK vers SensorData. Pas besoin de
configuration.SensorData.cs, ajoutez :
public ICollection<SensorValueHistory> Values { get; set; } = new List<SensorValueHistory>();
DbSet dans
AppDbContext.cs.
SensorValueHistory avec colonne SensorDataId apparaît.
dotnet ef migrations add AddSensorValueHistory
dotnet ef database update
Vous avez mis en place une persistance de données professionnelle avec des relations.
À retenir :
LocationId) + objet de navigation
(Location).ICollection croisées → table pivot
automatique.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 !
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).
DashboardData project with SensorService and Dependency Injection
(LAB 4).At the end of LAB 4, your project contained:
Models/SensorData.cs: a simple class with Name and Value.
Services/SensorService.cs: a service storing sensors in a
List<SensorData> in memory.
Services/ISensorService.cs: the service interface.Program.cs: DI registration
(AddScoped<ISensorService, SensorService>).⚠️ Problem: Data disappears on every restart! This LAB solves that.
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).
Our SensorData from LAB 4 only had Name and Value. But a database
requires:
[Key]) to identify each record.[Required], [StringLength]) for SQL
constraints.We will enrich the existing model and add new entities.
We will create 3 linked entities:
Location: the physical location of the sensor (1-to-N relationship).SensorData: the sensor itself.Tag: labels/categories (N-to-N relationship).Models/Location.csA location can contain multiple sensors (1-to-N relationship).
using System.ComponentModel.DataAnnotations;
namespace DashboardData.Models;
public class Location
{
public int Id { get; set; }
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>();
}
Models/Tag.csA 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
{
public int Id { get; set; }
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>();
}
Models/SensorData.csAdd annotations, the foreign key to Location, and the Tags
collection.
using System.ComponentModel.DataAnnotations;
namespace DashboardData.Models;
public class SensorData
{
// Primary Key (PK)
public int Id { get; set; }
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>();
}
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!
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
}
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();
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:
app.db to see 4 tables:
Sensors, Locations, Tags, and SensorDataTag (pivot
table generated automatically).
Now that the pipe is laid, let's fill it up.
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).
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:
SensorValueHistory are: Id (PK),
Value (double), Timestamp (DateTime), SensorDataId (FK),
SensorData (navigation).
SensorDataId, EF Core
automatically understands it's the FK to SensorData. No extra
configuration needed.
SensorData.cs, add:
public ICollection<SensorValueHistory> Values { get; set; } = new List<SensorValueHistory>();
DbSet in AppDbContext.cs.
SensorValueHistory
table with a SensorDataId column appears.
dotnet ef migrations add AddSensorValueHistory
dotnet ef database update
You have set up professional data persistence with relationships.
Key Takeaways:
LocationId) + navigation object
(Location).ICollections → automatic pivot table.
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!