Entity Framework (EF) s'est imposé comme le standard du mapping objet-relationnel (ORM) pour les développeurs .NET. Son approche "Code-First" permet de concevoir une base de données en se concentrant uniqeument sur la structure des classes C#. L'un des scénarios les plus courants, mais parfois mal compris, est la gestion des relations plusieurs-à-plusieurs (Many-to-Many).
Dans cet exemple, nous allons modéliser un système académique simple où un Apprenant peut s'inscrire à plusieurs Modules, et chaque Module peut accueillir plusieurs Apprenants.
1. Définition des entités
Pour établir une relation de type "Many-to-Many" de manière fluide, chaque classe doit contenir une propriété de navigation sous forme de collection (ICollection) pointant vers l'autre classe.
namespace EcoleDemo.Entities
{
public enum Genre { Masculin, Feminin }
public class Apprenant
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string NomComplet { get; set; }
public Genre Sexe { get; set; }
public DateTime? DateInscription { get; set; }
// Propriété de navigation
public virtual ICollection<Module> Modules { get; set; }
}
public class Module
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ModuleId { get; set; }
public string Intitule { get; set; }
// Propriété de navigation
public virtual ICollection<Apprenant> Apprenants { get; set; }
}
}
L'utilisation du mot-clé virtual est cruciale ici : elle permet au moteur d'Entity Framework d'activer le chargement différé (Lazy Loading) et de gérer dynamiquement les relations entre les objets.
2. Configuration du contexte de données
Le DbContext est la passerelle entre vos objets C# et la base de données SQL. Nous définissons ici les DbSet pour nos entités.
using System.Data.Entity;
namespace EcoleDemo.DAL
{
public class EcoleContext : DbContext
{
public EcoleContext() : base("name=MaConnexionSql") { }
public DbSet<Apprenant> Apprenants { get; set; }
public DbSet<Module> Modules { get; set; }
}
}
Il est nécessaire de configurer la chaîne de connexion dans le fichier App.config ou Web.config pour spécifier le serveur cible :
<connectionStrings>
<add name="MaConnexionSql"
connectionString="Data Source=.;Initial Catalog=EcoleDb;Integrated Security=True;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
3. Manipulation des données et relations
Lors de l'exécution, Entity Framework détecte automatiquement la double collection et crée une table de jointure intermédiaire dans la base de données (souvent nommée ApprenantModules). Voici comment manipuler ces relations en code :
class Program
{
static void Main(string[] args)
{
using (var context = new EcoleContext())
{
// 1. Initialisation des données de base
if (!context.Modules.Any())
{
context.Modules.AddRange(new List<Module>
{
new Module { Intitule = "Architecture logicielle" },
new Module { Intitule = "Base de données NoSQL" },
new Module { Intitule = "Développement Cloud" }
});
}
// 2. Création d'un nouvel apprenant
var nouvelEleve = new Apprenant
{
NomComplet = "Jean Dupont",
Sexe = Genre.Masculin,
Modules = new List<Module>()
};
// 3. Association : On récupère des modules existants pour les assigner
var modulesSelectionnes = context.Modules
.Where(m => m.Intitule.Contains("Architecture") || m.Intitule.Contains("Cloud"))
.ToList();
foreach (var mod in modulesSelectionnes)
{
nouvelEleve.Modules.Add(mod);
}
context.Apprenants.Add(nouvelEleve);
// 4. Persistance des changements
context.SaveChanges();
Console.WriteLine("Données enregistrées avec succès.");
}
}
}
Fonctionnement interne
L'avantage majeur de cette approche réside dans l'automatisation. EF gère l'intégrité référentielle sans que vous ayez à écrire de requêtes SQL INSERT complexes pour la table de liaison. Lorsque vous ajoutez un Module à la collection Modules d'un Apprenant, EF génère automatiquement une ligne dans la table de jointure avec les identifiants respectifs.
Si vous examinez votre instance SQL Server après l'exécution, vous constaterez la création d'une table comportant deux colonnes (par exemple Apprenant_Id et Module_ModuleId), agissant comme des clés étrangères vers les tables principales.