用途
改 SQL,缓存结果,审计日志,自动事务提交等。
类型
- IDbCommandInterceptor:创建、执行、失败、释放
- IDbConnectionInterceptor:打开和关闭连接、连接失败
- IDbTransactionInterceptor:创建事务、使用事务、提交事务、回滚事务、创建和使用保存点、事务失败
- ISaveChangesInterceptor:提交变更
基类 DbCommandInterceptor、 DbConnectionInterceptor、DbTransactionInterceptor、SaveChangesInterceptor 包含对应接口中每个方法的无操作实现,以避免实现不需要的方法。
DbCommandInterceptor
在 SQL 发送到数据库之前,对 SQL 进行修改,通常结合 EF Core 查询标记来标记每个需要修改的查询。
1
| var blogs1 = context.Blogs.TagWith("Use hint: robust plan").ToList();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TaggedQueryCommandInterceptor : DbCommandInterceptor { public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default) { ManipulateCommand(command); return new ValueTask<InterceptionResult<DbDataReader>>(result); }
private static void ManipulateCommand(DbCommand command) { if (command.CommandText.StartsWith("-- Use hint: robust plan", StringComparison.Ordinal)) { command.CommandText += " OPTION (ROBUST PLAN)"; } } }
|
结果
1 2
| SELECT [b].[Id], [b].[Name] FROM [Blogs] AS [b] OPTION (ROBUST PLAN)
|
SaveChangesInterceptor
- 用于审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public async ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { _audit = CreateAudit(eventData.Context); using var auditContext = new AuditContext(_connectionString); auditContext.Add(_audit); await auditContext.SaveChangesAsync(); return result; }
public InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result) { _audit = CreateAudit(eventData.Context); using var auditContext = new AuditContext(_connectionString); auditContext.Add(_audit); auditContext.SaveChanges(); return result; }
|
同时重写同步和异步方法可确保在无论是否调用 SaveChanges 或 SaveChangesAsync 的情况下进行审计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| private static SaveChangesAudit CreateAudit(DbContext context) { context.ChangeTracker.DetectChanges(); var audit = new SaveChangesAudit { AuditId = Guid.NewGuid(), StartTime = DateTime.UtcNow }; foreach (var entry in context.ChangeTracker.Entries()) { var auditMessage = entry.State switch { EntityState.Deleted => CreateDeletedMessage(entry), EntityState.Modified => CreateModifiedMessage(entry), EntityState.Added => CreateAddedMessage(entry), _ => null };
if (auditMessage != null) { audit.Entities.Add(new EntityAudit { State = entry.State, AuditMessage = auditMessage }); } }
return audit;
string CreateAddedMessage(EntityEntry entry) => entry.Properties.Aggregate( $"Inserting {entry.Metadata.DisplayName()} with ", (auditString, property) => auditString + $"{property.Metadata.Name}: '{property.CurrentValue}' ");
string CreateModifiedMessage(EntityEntry entry) => entry.Properties.Where(property => property.IsModified || property.Metadata.IsPrimaryKey()).Aggregate( $"Updating {entry.Metadata.DisplayName()} with ", (auditString, property) => auditString + $"{property.Metadata.Name}: '{property.CurrentValue}' ");
string CreateDeletedMessage(EntityEntry entry) => entry.Properties.Where(property => property.Metadata.IsPrimaryKey()).Aggregate( $"Deleting {entry.Metadata.DisplayName()} with ", (auditString, property) => auditString + $"{property.Metadata.Name}: '{property.CurrentValue}' "); }
|
- 自动设置实体值,如 CreatedBy,CreatedDT 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class MySaveChangesInterceptor : SaveChangesInterceptor { private readonly IAppUser _appUser;
public MySaveChangesInterceptor (IAppUser appUser) { _appUser = appUser; }
public async ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { foreach (var entry in eventData.Context.ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Added: InterceptAddedOperation(entry); break; case EntityState.Modified: InterceptModifiedOperation(entry); break; case EntityState.Deleted: InterceptDeletedOperation(entry); break; } } return result; }
protected virtual void InterceptAddedOperation(EntityEntry entry) { if (entry.Entity is IAuditedCreationEntity) { (entry.Entity as IAuditedCreationEntity).CreatedBy = _appUser?.UserId ?? string.Empty; (entry.Entity as IAuditedCreationEntity).CreatedDT = DateTime.Now; } } protected virtual void InterceptModifiedOperation(EntityEntry entry) { if (entry.Entity is IAuditedUpdateEntity) { (entry.Entity as IAuditedUpdateEntity).ModifiedBy = _appUser?.UserId ?? string.Empty; (entry.Entity as IAuditedUpdateEntity).ModifiedDT = DateTime.Now; } }
protected virtual void InterceptDeletedOperation(EntityEntry entry) { } }
|
注册
配置 DbContext 实例,在 DbContext.OnConfiguring 中完成注册。
1 2 3 4 5
| public class ExampleContext : BlogsContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.AddInterceptors(new TaggedQueryCommandInterceptor()); }
|