Dapper et Relation M:N
Le multi-mapping de Dapper, tel que montré de la façon suivante...
public async Task<List<Evenement>> GetAll()
{
string sql = @"
SELECT * from evenements e
INNER JOIN villes v on e.ville_id = v.id;
";
using (IDbConnection connection = _dbContext.CreateConnection())
{
IEnumerable<Evenement> evenements = await connection.QueryAsync<Evenement, Ville, Evenement>(sql,
(evenement, ville) =>
{
evenement.Ville = ville;
return evenement;
});
return evenements.ToList();
}
}
... fonctionne bien pour les associations 1:1. C'est-à-dire que la jointure retourne un seul résultat.
En effet, le multimapping est fait pour tronquer une rangée de résultats en plusieurs objets. Par exemple, dans l'exemple suivant où un événement est associé à une ville, Dapper peut très bien faire la coupure:
Cependant, imaginons que vous avez une relation M:N (plusieurs à plusieurs) entre des événements et des catégories. Pour chaque catégorie associée, une rangée est retournée, mais pour le même événement!
Effectuer la boucle comme montré précédemment aura comme effet de dupliquer vos résultats dans la vue puisque vous recevez au fond plusieurs événements, même s'il s'agit en fait du même.
Utiliser un dictionnaire
L'exemple ici est pour le cas le plus complexe où vous retournez une liste d'éléments (ex.: des événements), qui contiennent eux aussi des listes (ex.: categories
).
Un dictionnaire permet de faire l'association entre des clés et des valeurs. Par exemple, un id
et un objet
.
L'idée ici est de garder une référence à votre objet cible (ex.: evenement
, produit
, etc.) dans le dictionnaire. À chaque fois que Dapper essaie de faire une association, vous vérifiez sur vous avez déjà une référence à l'objet cible. Si oui, vous le récupérez et utilisez cette version pour faire les associations. Cela évitera de dupliquer.
-
Par exemple, avant de faire la requête, on crée un dictionnaire faisant l'association entre des
int
(id
) et l'objet cible (Evenement
):var evenementsDictionnaire = new Dictionary<int, Evenement>();
using (IDbConnection connection = _dbContext.CreateConnection()) -
Ensuite, lorsque vous faites la requête à l'aide du Multi-Mapping, pour chaque rangée de résultats reçus, on vérifie si cet objet est dans le dictionnaire. Si oui, on le récupère, sinon, on l'ajoute au dictionnaire.
await connection.QueryAsync<Evenement, Categorie, Evenement>(
sql, (evenement, categorie) =>
{
Evenement evenementExistant;
// Si le dictionnaire contient déjà une référence à l'objet cible, correspondant à son id, on récupère l'objet.
if (evenementsDictionnaire.ContainsKey(evenement.Id))
{
evenementExistant = evenementsDictionnaire[evenement.Id];
}
else // Sinon, on l'ajoute au dictionnaire
{
evenementExistant = evenement;
evenementsDictionnaire.Add(evenement.Id, evenementExistant);
}
//...Donc, pour un même id, on aura toujours une référence au même objet.
-
On ajoute ensuite à la liste voulue (ex.:
categories
), en s'assurant de ne pas faire de double entrée.if(!evenementExistant.Categories.Any(c => c.Id == categorie.Id))
evenementExistant.Categories.Add(categorie);
return evenementExistant; -
Votre fonction de repository devrait ensuite retourner les valeurs du dictionnaire.
return evenementsDictionnaire.Values.ToList();