BulkOptions dans DapperPlus : Le guide essentiel
DapperPlus est une extension du micro-ORM Dapper qui permet de réaliser des opérations Bulk. Ces opérations s’appuient sur des flux structurés, ce qui les rend bien plus performantes qu’un insert classique. BulkOptions est la classe qui permet d’en configurer précisément le comportement.
C’est via cette classe que tu pilotes la stratégie de tes requêtes : gestion des conflits, filtrage des colonnes et suivi d’impact.
C’est quoi UseBulkOptions ?
DapperPlus te permet de faire des opérations en masse (Bulk) en une seule passe SQL. Par défaut, il devine comment mapper tes entités. UseBulkOptions te laisse surcharger ce comportement : colonnes à ignorer, gestion des conflits, logs, comptage des lignes modifiées, etc.
Tu peux configurer les options à deux endroits.
Sur le mapping — persistant (context global), appliqué à chaque appel pour ce type :
DapperPlusManager.Entity<Product>()
.Table("Product")
.Identity(x => x.ProductID)
.UseBulkOptions(x => x.InsertIfNotExists = true);
connection.BulkInsert(products);
Sur la connexion — ponctuel, appliqué uniquement à la chaîne qui suit :
connection
.UseBulkOptions(o => o.Log = s => Console.WriteLine(s))
.BulkMerge(products);
graph TD
A[Configuration de BulkOptions] --> B{Où configurer ?}
B -- Mapping --> C[DapperPlusManager.Entity]
B -- Connexion --> D[connection.UseBulkOptions]
C --> C1[<b>Persistant</b><br/>Règles métier, colonnes à ignorer]
D --> D1[<b>Ponctuel</b><br/>Debug, Log, Timeout, BatchSize]
La règle simple : mapping pour les règles métier, connexion pour le debug ou les options techniques.
Les cas courants
Les champs d’audit
Problème classique : tu as CreatedDate et LastUpdatedDate. Tu ne veux pas écraser CreatedDate quand tu mets à jour et tu ne veux pas insérer LastUpdatedDate sur une nouvelle ligne.
DapperPlusManager.Entity<Product>()
.Table("Product")
.Identity(x => x.ProductID)
.UseBulkOptions(x => {
// Ne pas écraser CreatedDate lors d'un UPDATE
x.IgnoreOnMergeUpdateExpression = y => new { y.CreatedDate };
// Ne pas insérer LastUpdatedDate lors d'un INSERT
x.IgnoreOnMergeInsertExpression = y => new { y.LastUpdatedDate };
});
connection.BulkMerge(products); // ✅ Les champs d'audit sont respectés
Mettre à jour seulement si quelque chose a changé
Par défaut, BulkMerge écrit en base même si les données sont identiques. Pour n’écrire que si au moins une colonne a changé :
DapperPlusManager.Entity<Product>()
.Table("Product")
.Identity(x => x.ProductID)
.UseBulkOptions(x => {
// MAJ si la ligne match ET qu'au moins une de ces colonnes est différente
x.MergeMatchedAndOneNotConditionExpression = y => new { y.Name, y.Description };
});
connection.BulkMerge(products);
// ✅ N'écrit en base que si Name ou Description a changé
💡 Note sur les conditions : Il est important de distinguer
MergeMatchedAndOneNotConditionExpression(qui déclenche la mise à jour si au moins une des colonnes listées a changé) deMergeMatchedAndConditionExpression(qui exige que toutes les conditions spécifiées soient vraies).
Utile pour ne pas déclencher des triggers ou des audits inutiles.
Récupérer le nombre de lignes affectées
Par défaut, les méthodes bulk ne retournent rien. Pour savoir combien de lignes ont été insérées ou mises à jour, active UseRowsAffected et passe un objet ResultInfo :
var resultInfo = new BulkOperationResultInfo();
connection
.UseBulkOptions(o => {
o.UseRowsAffected = true;
o.ResultInfo = resultInfo;
})
.BulkMerge(products);
Console.WriteLine($"Insérés : {resultInfo.RowsInserted}");
Console.WriteLine($"Mis à jour : {resultInfo.RowsUpdated}");
Console.WriteLine($"Supprimés : {resultInfo.RowsDeleted}");
⚠️ Activer
UseRowsAffecteda un coût en performance. Un aller-retour supplémentaire est nécessaire pour récupérer les stats. Ne l’active que si tu en as besoin.
Insérer seulement si absent
Tu ne veux pas mettre à jour les lignes existantes — juste les ignorer et insérer les nouvelles. InsertIfNotExists est fait pour ça :
DapperPlusManager.Entity<Product>()
.Table("Product")
.Identity(x => x.ProductID)
.UseBulkOptions(x => x.InsertIfNotExists = true);
connection.BulkInsert(products);
// ✅ Insère uniquement les nouveaux — les existants sont intacts
Autoriser les clés dupliquées
Si tu souhaites ignorer les erreurs liées aux clés dupliquées lors d’une opération, tu peux utiliser l’option AllowDuplicateKeys. Cela permet de continuer l’insertion des autres enregistrements sans faire échouer toute la transaction à cause d’un doublon.
connection.UseBulkOptions(x =>
x.AllowDuplicateKeys = true
).BulkInsert(products);
Les pièges dans lesquels tu ne dois pas tomber
❌ InsertIfNotExists c’est pareil que BulkMerge ? Non. InsertIfNotExists ignore les lignes existantes. BulkMerge les met à jour. Ce sont deux comportements opposés sur les doublons.
❌ UseRowsAffected s’active une fois pour toutes ? Non, il doit être spécifié sur chaque appel à UseBulkOptions. Comme ConfigureAwait, chaque opération doit le déclarer explicitement.
// ❌ Incorrect — ResultInfo restera vide
var resultInfo = new BulkOperationResultInfo();
connection.BulkMerge(products);
// ✅ Correct
connection
.UseBulkOptions(o => {
o.UseRowsAffected = true;
o.ResultInfo = resultInfo;
})
.BulkMerge(products);
❌ Les options sur la connexion s’appliquent à tous les types ? Non. Depuis la connexion, seules les options génériques sont disponibles (log, batch size, timeout…). Les règles métier (colonnes ignorées, conditions de merge) se configurent via le mapping.
En résumé
| Besoin | Option(s) | Méthode |
|---|---|---|
| Insert seulement si absent | InsertIfNotExists |
BulkInsert |
| Ignorer les doublons de clés | AllowDuplicateKeys |
Toutes |
| Protéger les champs d’audit | IgnoreOnMergeInsert/UpdateExpression |
BulkMerge |
| Compter les lignes modifiées | UseRowsAffected + ResultInfo |
Toutes |
| Mettre à jour seulement si changé | MergeMatchedAndOneNotConditionExpression |
BulkMerge |
| Déboguer le SQL généré | Log |
Toutes |
Conclusion
Tu n’as ici qu’un aperçu des possibilités offertes par BulkOptions. C’est un outil extrêmement complet et puissant qui permet de répondre à des scénarios de synchronisation de données complexes avec une finesse chirurgicale. Que ce soit pour des optimisations de performance extrêmes, des logiques métier d’audit ou des gestions de conflits avancées, BulkOptions offre le contrôle nécessaire pour maîtriser tes flux de données à grande échelle.
La documentation liste plus de 100 exemples.
Dans la pratique, celles du tableau ci-dessus couvrent 90% des cas. Pour aller plus loin : dapper-plus.net/options.
Pour en savoir plus sur les fonctionnalités d’audit : dapper-plus.net/audit