Les intercepteurs d'Entity Framework Core représentent un outil puissant pour intercepter et modifier les opérations de base de données avant ou après leur exécution. Dans cet article, nous explorerons en détail comment utiliser les intercepteurs EF Core pour améliorer la traçabilité, la performance et la maintenance de vos applications .NET.
Comprendre les Intercepteurs Entity Framework
Les intercepteurs permettent d'injecter une logique personnalisée dans le pipeline d'exécution d'EF Core. Ils peuvent être utilisés pour :
- Logger les requêtes SQL
- Modifier les commandes avant leur exécution
- Implémenter des stratégies de retry
- Gérer des audits automatiques
Types d'Intercepteurs Disponibles
EF Core propose plusieurs types d'intercepteurs :
// 1. DbCommandInterceptor
public class LoggingInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
Log.Information($"Executing SQL: {command.CommandText}");
return result;
}
}
// 2. SaveChangesInterceptor
public class AuditInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var entries = eventData.Context.ChangeTracker
.Entries()
.Where(e => e.State == EntityState.Modified);
foreach (var entry in entries)
{
entry.Property("LastModified").CurrentValue = DateTime.UtcNow;
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
Configuration des Intercepteurs
L'enregistrement des intercepteurs se fait lors de la configuration du DbContext :
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(connectionString)
.AddInterceptors(new LoggingInterceptor())
.AddInterceptors(new AuditInterceptor());
}
}
Cas d'Usage Pratiques
1. Audit Automatique
public class AuditableEntity
{
public DateTime CreatedAt { get; set; }
public string CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public string LastModifiedBy { get; set; }
}
public class AuditableInterceptor : SaveChangesInterceptor
{
private readonly ICurrentUserService _currentUserService;
public AuditableInterceptor(ICurrentUserService currentUserService)
{
_currentUserService = currentUserService;
}
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
UpdateEntities(eventData.Context);
return result;
}
private void UpdateEntities(DbContext context)
{
var entries = context.ChangeTracker.Entries<AuditableEntity>();
foreach (var entry in entries)
{
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedAt = DateTime.UtcNow;
entry.Entity.CreatedBy = _currentUserService.UserId;
}
if (entry.State == EntityState.Modified)
{
entry.Entity.LastModified = DateTime.UtcNow;
entry.Entity.LastModifiedBy = _currentUserService.UserId;
}
}
}
}
2. Gestion des Soft Deletes
public interface ISoftDeletable
{
bool IsDeleted { get; set; }
DateTime? DeletedAt { get; set; }
}
public class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
var entries = eventData.Context.ChangeTracker
.Entries<ISoftDeletable>()
.Where(e => e.State == EntityState.Deleted);
foreach (var entry in entries)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
entry.Entity.DeletedAt = DateTime.UtcNow;
}
return result;
}
}
Bonnes Pratiques
- Gardez les intercepteurs légers et focalisés sur une seule responsabilité
- Évitez les opérations bloquantes dans les intercepteurs
- Utilisez la journalisation appropriée pour le debugging
- Implémentez des timeouts appropriés
Tests des Intercepteurs
public class AuditInterceptorTests
{
[Fact]
public async Task ShouldSetAuditFieldsOnSave()
{
// Arrange
var options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase("TestDb")
.AddInterceptors(new AuditableInterceptor(new MockCurrentUserService()))
.Options;
// Act
using (var context = new TestDbContext(options))
{
var entity = new TestEntity { Name = "Test" };
context.TestEntities.Add(entity);
await context.SaveChangesAsync();
// Assert
Assert.NotNull(entity.CreatedAt);
Assert.NotNull(entity.CreatedBy);
}
}
}
Considérations de Performance
Pour optimiser les performances lors de l'utilisation des intercepteurs :
- Utilisez des pools de connexions appropriés
- Implémentez du caching si nécessaire
- Évitez les requêtes supplémentaires dans les intercepteurs
- Utilisez des profilers pour identifier les goulots d'étranglement
Conclusion
Les intercepteurs Entity Framework constituent un outil puissant pour étendre et personnaliser le comportement d'EF Core. En suivant les bonnes pratiques et en comprenant leurs cas d'usage appropriés, ils peuvent grandement améliorer la maintenabilité et la robustesse de vos applications .NET.
Points clés à retenir :
- Les intercepteurs permettent d'injecter une logique personnalisée dans le pipeline EF Core
- Ils sont particulièrement utiles pour l'audit, le logging et la gestion des soft deletes
- La performance doit être surveillée lors de leur utilisation
- Les tests sont essentiels pour garantir leur bon fonctionnement