🌐
🔍 100%
👁️

🎙️ Sélectionner une voix

🎙️ Select a voice

TP 6 : Connexion UI - Base de Données (Lecture & LINQ to SQL)

Module : Programmation .Net C#

Durée : 2h

Objectif : Connecter le Service métier à Entity Framework Core. Remplacer les fausses données par des requêtes SQL réelles via LINQ, et gérer l'asynchronisme pour fluidifier l'interface utilisateur.

Pré-requis

Activité 1 : Injection du DbContext dans le Service (25 min)

Actuellement, notre SensorService possède une liste codée en dur. Nous allons la supprimer et demander à ASP.NET de nous fournir le AppDbContext pour interroger SQLite.

UI (Blazor) @inject ISensorService SensorService Logique Métier AppDbContext EF Core (ORM) SQLite DB

L'architecture de bout en bout de l'application

1. Ouvrez le fichier Services/SensorService.cs.

2. Supprimez complètement la fausse liste _sensors.

3. Ajoutez un constructeur pour injecter le AppDbContext :

using DashboardData.Models;
using DashboardData.Data; // Pour accéder à AppDbContext
using Microsoft.EntityFrameworkCore; // Indispensable pour les méthodes Async d'EF Core

namespace DashboardData.Services;

public class SensorService : ISensorService
{
    private readonly AppDbContext _context;

    // L'Injection de dépendance fait le travail ici :
    // Quand Blazor crée le SensorService, il lui passe automatiquement le DbContext.
    public SensorService(AppDbContext context)
    {
        _context = context;
    }

    // ... la suite au prochain point
}

Activité 2 : Requêtes Asynchrones et Jointures (EF Core) (25 min)

Une base de données est un système externe. La lecture prend du temps. Il faut utiliser des méthodes asynchrones (Async) pour ne pas bloquer l'interface Web pendant la requête.

1. Mettez à jour la méthode GetSensorsAsync dans SensorService.cs. Nous allons utiliser Include pour récupérer la localisation associée à chaque capteur :

public async Task<List<SensorData>> GetSensorsAsync()
{
    // EF Core traduit Include par un JOIN SQL vers la table Location
    return await _context.Sensors
        .Include(s => s.Location) 
        .ToListAsync();
}

2. Mettez également à jour la méthode d'ajout :

public async Task AddSensorAsync(SensorData sensor)
{
    // 1. On prépare l'ajout en mémoire
    _context.Sensors.Add(sensor);
    
    // 2. On valide la transaction (Génère le INSERT INTO SQL)
    await _context.SaveChangesAsync(); 
}

(⚠️ Assurez-vous que l'interface ISensorService correspond bien à ces signatures asynchrones !)

3. Testez l'application : Lancez dotnet watch.

Activité 3 : Filtrage LINQ directement en Base (20 min)

La puissance d'EF Core est de traduire le code C# en SQL. Si vous filtrez avec LINQ, le filtrage se fera dans le moteur SQLite, ce qui est infiniment plus rapide que de tout charger en RAM.

1. Dans ISensorService.cs, ajoutez la signature d'une nouvelle méthode :

Task<List<SensorData>> GetCriticalSensorsAsync(double threshold);

2. Dans SensorService.cs, implémentez cette méthode :

public async Task<List<SensorData>> GetCriticalSensorsAsync(double threshold)
{
    return await _context.Sensors
        .Include(s => s.Location)
        .Where(s => s.Value > threshold) // Traduit en : WHERE Value > @threshold
        .OrderByDescending(s => s.Value) // Traduit en : ORDER BY Value DESC
        .ToListAsync();                  // Déclenche l'exécution SQL
}
SELECT "s"."Id", "s"."Name", "s"."Value", "s"."LocationId", "l"."Id", "l"."Name" FROM "Sensors" AS "s" LEFT JOIN "Locations" AS "l" ON "s"."LocationId" = "l"."Id" WHERE "s"."Value" > @__threshold_0 ORDER BY "s"."Value" DESC

Activité 4 : Mise à jour du Dashboard (Blazor) (20 min)

Intégrons cette nouvelle fonctionnalité d'alerte et affichons la localisation dans notre vue.

1. Ouvrez Pages/MyDashboard.razor.

2. Ajoutez des boutons pour basculer les filtres et adaptez le tableau :

@page "/dashboard"
@rendermode InteractiveServer
@using DashboardData.Models
@using DashboardData.Services
@inject ISensorService SensorService 

<button class="btn btn-secondary mb-3" @onclick="LoadAll">Toutes les sondes</button>
<button class="btn btn-danger mb-3" @onclick="LoadCritical">Alertes (> 30.0)</button>

@if (Sensors == null)
{
    <p>Chargement en cours...</p>
}
else
{
    <table class="table table-striped">
        <thead>
            <tr>
                <th>Capteur</th>
                <th>Lieu</th>
                <th>Valeur Courante</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var sensor in Sensors)
            {
                <tr>
                    <td>@sensor.Name</td>
                    <!-- Grâce au Include(), Location n'est pas nul ! -->
                    <td>@(sensor.Location?.Name ?? "Non défini")</td>
                    <td>@sensor.Value</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private List<SensorData>? Sensors;

    protected override async Task OnInitializedAsync()
    {
        await LoadAll();
    }

    private async Task LoadAll()
    {
        Sensors = await SensorService.GetSensorsAsync();
    }

    private async Task LoadCritical()
    {
        Sensors = await SensorService.GetCriticalSensorsAsync(30.0);
    }
}

🚀 Exercices d'application (En autonomie)

Exercice 1 : Calculs d'agrégation SQL via EF Core

Au lieu de ramener toutes les lignes en mémoire pour faire une moyenne, demandez à SQLite de faire le calcul et de vous renvoyer un seul nombre (très optimisé !).

1. Dans ISensorService et SensorService, créez trois nouvelles méthodes asynchrones :

Exemple d'implémentation pour la moyenne :

public async Task<double> GetAverageValueAsync()
{
    // Si la base est vide, AverageAsync plante. On met une sécurité.
    if (!await _context.Sensors.AnyAsync()) return 0;
    
    return await _context.Sensors.AverageAsync(s => s.Value);
}

Exercice 2 : Affichage des KPIs (UI)

Dans MyDashboard.razor, affichez ces trois indicateurs sous forme de "Cartes" (Cards Bootstrap) au-dessus de votre tableau.

  1. Dans la zone @code, déclarez trois variables (TotalSondes, Moyenne, Max).
  2. Dans la méthode OnInitializedAsync, appelez vos nouveaux services pour remplir ces variables.
  3. Dans le HTML, utilisez ce template Bootstrap pour faire un bel affichage :
<div class="row mb-4">
    <div class="col-md-4">
        <div class="card text-white bg-primary">
            <div class="card-body">
                <h5 class="card-title">Total Sondes</h5>
                <h2>@TotalSondes</h2>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="card text-white bg-success">
            <div class="card-body">
                <h5 class="card-title">Moyenne Globale</h5>
                <h2>@Moyenne.ToString("F1") °C</h2> <!-- F1 = 1 chiffre après la virgule -->
            </div>
        </div>
    </div>
    <!-- Faites la même chose pour le MAX en rouge (bg-danger) -->
</div>

🏁 Synthèse du TP 6

Félicitations, vous avez maintenant une application "Full-Stack" complète : Base de données ↔ Service Métier ↔ Interface Web.

LAB 6: UI - Database Connection (Read & LINQ to SQL)

Module: C# .Net Programming

Duration: 2h

Objective: Connect the business Service to Entity Framework Core. Replace mock data with real SQL queries via LINQ, and manage asynchrony to keep the user interface responsive.

Prerequisites

Activity 1: Injecting DbContext into the Service (25 min)

Currently, our SensorService has a hardcoded list. We are going to delete it and ask ASP.NET to provide us with the AppDbContext to query SQLite.

UI (Blazor) @inject ISensorService SensorService Business Logic AppDbContext EF Core (ORM) SQLite DB

End-to-End Application Architecture

1. Open the file Services/SensorService.cs.

2. Completely delete the fake _sensors list.

3. Add a constructor to inject the AppDbContext:

using DashboardData.Models;
using DashboardData.Data; // To access AppDbContext
using Microsoft.EntityFrameworkCore; // Essential for EF Core Async methods

namespace DashboardData.Services;

public class SensorService : ISensorService
{
    private readonly AppDbContext _context;

    // Dependency Injection does the work here:
    // When Blazor creates the SensorService, it automatically passes the DbContext to it.
    public SensorService(AppDbContext context)
    {
        _context = context;
    }

    // ... continuation in the next point
}

Activity 2: Asynchronous Queries and Joins (EF Core) (25 min)

A database is an external system. Reading takes time. You must use asynchronous methods (Async) so as not to block the Web interface during the query.

1. Update the GetSensorsAsync method in SensorService.cs. We'll use Include to retrieve the related Location for each sensor:

public async Task<List<SensorData>> GetSensorsAsync()
{
    // EF Core translates Include into a SQL JOIN to the Location table
    return await _context.Sensors
        .Include(s => s.Location) 
        .ToListAsync();
}

2. Also update the add method:

public async Task AddSensorAsync(SensorData sensor)
{
    // 1. Prepare the addition in memory
    _context.Sensors.Add(sensor);
    
    // 2. Validate the transaction (Generates the SQL INSERT INTO)
    await _context.SaveChangesAsync(); 
}

(⚠️ Make sure the ISensorService interface reflects these async signatures!)

3. Test the application: Run dotnet watch.

Activity 3: LINQ Filtering Directly in the Database (20 min)

The power of EF Core is translating C# code into SQL. If you filter with LINQ, the filtering will happen in the SQLite engine, which is infinitely faster than loading everything into RAM.

1. In ISensorService.cs, add the signature for a new method:

Task<List<SensorData>> GetCriticalSensorsAsync(double threshold);

2. In SensorService.cs, implement this method:

public async Task<List<SensorData>> GetCriticalSensorsAsync(double threshold)
{
    return await _context.Sensors
        .Include(s => s.Location)
        .Where(s => s.Value > threshold) // Translated to: WHERE Value > @threshold
        .OrderByDescending(s => s.Value) // Translated to: ORDER BY Value DESC
        .ToListAsync();                  // Triggers SQL execution
}
SELECT "s"."Id", "s"."Name", "s"."Value", "s"."LocationId", "l"."Id", "l"."Name" FROM "Sensors" AS "s" LEFT JOIN "Locations" AS "l" ON "s"."LocationId" = "l"."Id" WHERE "s"."Value" > @__threshold_0 ORDER BY "s"."Value" DESC

Activity 4: Updating the Dashboard (Blazor) (20 min)

Let's update our UI to display this alert feature along with the location data.

1. Open Pages/MyDashboard.razor.

2. Add buttons to toggle filters and adapt the table:

@page "/dashboard"
@rendermode InteractiveServer
@using DashboardData.Models
@using DashboardData.Services
@inject ISensorService SensorService 

<button class="btn btn-secondary mb-3" @onclick="LoadAll">All Sensors</button>
<button class="btn btn-danger mb-3" @onclick="LoadCritical">Alerts (> 30.0)</button>

@if (Sensors == null)
{
    <p>Loading...</p>
}
else
{
    <table class="table table-striped">
        <thead>
            <tr>
                <th>Sensor</th>
                <th>Location</th>
                <th>Current Value</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var sensor in Sensors)
            {
                <tr>
                    <td>@sensor.Name</td>
                    <!-- Thanks to Include(), Location is not null! -->
                    <td>@(sensor.Location?.Name ?? "Undefined")</td>
                    <td>@sensor.Value</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private List<SensorData>? Sensors;

    protected override async Task OnInitializedAsync()
    {
        await LoadAll();
    }

    private async Task LoadAll()
    {
        Sensors = await SensorService.GetSensorsAsync();
    }

    private async Task LoadCritical()
    {
        Sensors = await SensorService.GetCriticalSensorsAsync(30.0);
    }
}

🚀 Application Exercises (Autonomous)

Exercise 1: SQL Aggregation Calculations via EF Core

Instead of bringing all rows into memory to calculate an average, you will ask SQLite to do the calculation and return a single number (highly optimized!).

1. In ISensorService and SensorService, create three new asynchronous methods:

Example implementation for the average:

public async Task<double> GetAverageValueAsync()
{
    // If the database is empty, AverageAsync crashes. We put a safeguard.
    if (!await _context.Sensors.AnyAsync()) return 0;
    
    return await _context.Sensors.AverageAsync(s => s.Value);
}

Exercise 2: Displaying KPIs (UI)

In MyDashboard.razor, display these three indicators as Bootstrap "Cards" above your table.

  1. In the @code area, declare three variables (TotalSensors, Average, Max).
  2. In the OnInitializedAsync method, call your new services to fill these variables.
  3. In the HTML, use this Bootstrap template for a nice display:
<div class="row mb-4">
    <div class="col-md-4">
        <div class="card text-white bg-primary">
            <div class="card-body">
                <h5 class="card-title">Total Sensors</h5>
                <h2>@TotalSensors</h2>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="card text-white bg-success">
            <div class="card-body">
                <h5 class="card-title">Global Average</h5>
                <h2>@Average.ToString("F1") °C</h2> <!-- F1 = 1 decimal place -->
            </div>
        </div>
    </div>
    <!-- Do the same for MAX in red (bg-danger) -->
</div>

🏁 TP 6 Synthesis

Congratulations, you now have a complete "Full-Stack" application: Database ↔ Business Service ↔ Web Interface.