EFCore 5 新特性 SaveChangesInterceptor
EFCore 5 新特性 SaveChangesInterceptor
Intro
之前 EF Core 5 還沒正式發(fā)布的時(shí)候有發(fā)布過一篇關(guān)于 SaveChangesEvents 的文章,有需要看可以移步到 efcore 新特性 SaveChanges Events,在后面的版本中又加入了 Interceptor 的支持,可以更方便的實(shí)現(xiàn) SaveChanges 事件的復(fù)用, 今天主要介紹一下通過 SaveChangesInterceptor 來實(shí)現(xiàn)日志審計(jì)
SaveChangesInterceptor
源碼實(shí)現(xiàn):
public?interface?ISaveChangesInterceptor?:?IInterceptor
{
????///?
????///?????Called?at?the?start?of? .
????///?
????///??Contextual?information?about?the? ?being?used.?
????///?
????///?????Represents?the?current?result?if?one?exists.
????///?????This?value?will?have? ?set?to? ?if?some?previous
????///?????interceptor?suppressed?execution?by?calling? .
????///?????This?value?is?typically?used?as?the?return?value?for?the?implementation?of?this?method.
????///?
????///?
????///?????If? ?is?false,?the?EF?will?continue?as?normal.
????///?????If? ?is?true,?then?EF?will?suppress?the?operation?it
????///?????was?about?to?perform?and?use? ?instead.
????///?????A?normal?implementation?of?this?method?for?any?interceptor?that?is?not?attempting?to?change?the?result
????///?????is?to?return?the? ?value?passed?in.
????///?
????InterceptionResult<int>?SavingChanges(
????????[NotNull]?DbContextEventData?eventData,
????????InterceptionResult<int>?result);
????///?
????///?????
????///?????????Called?at?the?end?of? .
????///?????
????///?????
????///?????????This?method?is?still?called?if?an?interceptor?suppressed?creation?of?a?command?in? .
????///?????????In?this?case,? ?is?the?result?returned?by? .
????///?????
????///?
????///??Contextual?information?about?the? ?being?used.?
????///?
????///?????The?result?of?the?call?to? .
????///?????This?value?is?typically?used?as?the?return?value?for?the?implementation?of?this?method.
????///?
????///?
????///?????The?result?that?EF?will?use.
????///?????A?normal?implementation?of?this?method?for?any?interceptor?that?is?not?attempting?to?change?the?result
????///?????is?to?return?the? ?value?passed?in.
????///?
????int?SavedChanges(
????????[NotNull]?SaveChangesCompletedEventData?eventData,
????????int?result);
????///?
????///?????Called?when?an?exception?has?been?thrown?in? .
????///?
????///??Contextual?information?about?the?failure.?
????void?SaveChangesFailed(
????????[NotNull]?DbContextErrorEventData?eventData);
????///?
????///?????Called?at?the?start?of? .
????///?
????///??Contextual?information?about?the? ?being?used.?
????///?
????///?????Represents?the?current?result?if?one?exists.
????///?????This?value?will?have? ?set?to? ?if?some?previous
????///?????interceptor?suppressed?execution?by?calling? .
????///?????This?value?is?typically?used?as?the?return?value?for?the?implementation?of?this?method.
????///?
????///??The?cancellation?token.?
????///?
????///?????If? ?is?false,?the?EF?will?continue?as?normal.
????///?????If? ?is?true,?then?EF?will?suppress?the?operation?it
????///?????was?about?to?perform?and?use? ?instead.
????///?????A?normal?implementation?of?this?method?for?any?interceptor?that?is?not?attempting?to?change?the?result
????///?????is?to?return?the? ?value?passed?in.
????///?
????ValueTaskint>>?SavingChangesAsync(
????????[NotNull]?DbContextEventData?eventData,
????????InterceptionResult<int>?result,
????????CancellationToken?cancellationToken?=?default);
????///?
????///?????
????///?????????Called?at?the?end?of? .
????///?????
????///?????
????///?????????This?method?is?still?called?if?an?interceptor?suppressed?creation?of?a?command?in? .
????///?????????In?this?case,? ?is?the?result?returned?by? .
????///?????
????///?
????///??Contextual?information?about?the? ?being?used.?
????///?
????///?????The?result?of?the?call?to? .
????///?????This?value?is?typically?used?as?the?return?value?for?the?implementation?of?this?method.
????///?
????///??The?cancellation?token.?
????///?
????///?????The?result?that?EF?will?use.
????///?????A?normal?implementation?of?this?method?for?any?interceptor?that?is?not?attempting?to?change?the?result
????///?????is?to?return?the? ?value?passed?in.
????///?
????ValueTask<int>?SavedChangesAsync(
????????[NotNull]?SaveChangesCompletedEventData?eventData,
????????int?result,
????????CancellationToken?cancellationToken?=?default);
????///?
????///?????Called?when?an?exception?has?been?thrown?in? .
????///?
????///??Contextual?information?about?the?failure.?
????///??The?cancellation?token.?
????///? ?A? ?representing?the?asynchronous?operation.?
????Task?SaveChangesFailedAsync(
????????[NotNull]?DbContextErrorEventData?eventData,
????????CancellationToken?cancellationToken?=?default);
}
為了比較方便的實(shí)現(xiàn)自己需要的 Interceptor,微軟還提供了一個(gè) SaveChangesInterceptor 抽象類,這樣只需要繼承于這個(gè)類,重寫自己需要的方法即可,實(shí)現(xiàn)比較簡單,就是實(shí)現(xiàn)了 ISaveChangesInterceptor 接口,然后接口的實(shí)現(xiàn)基本都是空的虛方法,根據(jù)需要重寫即可
源碼鏈接:https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Diagnostics/SaveChangesInterceptor.cs
使用 SaveChangesInterceptor 實(shí)現(xiàn)自動(dòng)審計(jì)
簡單寫了一個(gè)測試的審計(jì)攔截器
public?class?AuditInterceptor?:?SaveChangesInterceptor
{
????public?override?InterceptionResult<int>?SavingChanges(DbContextEventData?eventData,?InterceptionResult<int>?result)
????{
????????var?changesList?=?new?List();
????????foreach?(var?entry?in
?????????????????eventData.Context.ChangeTracker.Entries())
????????{
????????????if?(entry.State?==?EntityState.Added)
????????????{
????????????????changesList.Add(new?CompareModel()
????????????????????????????????{
????????????????????????????????????OriginalValue?=?null,
????????????????????????????????????NewValue?=?entry.CurrentValues.ToObject(),
????????????????????????????????});
????????????}
????????????else?if?(entry.State?==?EntityState.Deleted)
????????????{
????????????????changesList.Add(new?CompareModel()
????????????????????????????????{
????????????????????????????????????OriginalValue?=?entry.OriginalValues.ToObject(),
????????????????????????????????????NewValue?=?null,
????????????????????????????????});
????????????}
????????????else?if?(entry.State?==?EntityState.Modified)
????????????{
????????????????changesList.Add(new?CompareModel()
????????????????????????????????{
????????????????????????????????????OriginalValue?=?entry.OriginalValues.ToObject(),
????????????????????????????????????NewValue?=?entry.CurrentValues.ToObject(),
????????????????????????????????});
????????????}
????????????Console.WriteLine($"change?list:{changesList.ToJson()}");
????????}
????????return?base.SavingChanges(eventData,?result);
????}
????public?override?int?SavedChanges(SaveChangesCompletedEventData?eventData,?int?result)
????{
????????Console.WriteLine($"changes:{eventData.EntitiesSavedCount}");
????????return?base.SavedChanges(eventData,?result);
????}
????private?class?CompareModel
????{
????????public?object?OriginalValue?{?get;?set;?}
????????public?object?NewValue?{?get;?set;?}
????}
}
實(shí)際應(yīng)用的話還需要根據(jù)自己的場景做一些修改和測試
測試 DbContext 示例,這里使用了一個(gè)簡單的 InMemory 做了一個(gè)測試:
public?class?TestDbContext?:?DbContext
{
????public?TestDbContext(DbContextOptions?dbContextOptions )?:?base(dbContextOptions)
????{
????}
????public?DbSet?Posts?{?get;?set;?}
}
public?class?Post
{
????[Key]
????public?int?Id?{?get;?set;?}
????public?string?Author?{?get;?set;?}
????public?string?Title?{?get;?set;?}
????public?DateTime?PostedAt?{?get;?set;?}
}
測試代碼:
var?services?=?new?ServiceCollection();
services.AddDbContext(options?=>
{
????options.UseInMemoryDatabase("Tests")
????????//.LogTo(Console.WriteLine)?//?EF?Core?5?中新的更簡潔的日志記錄方式
????????.AddInterceptors(new?AuditInterceptor())
????????;
});
using?var?provider?=?services.BuildServiceProvider();
using?(var?scope?=?provider.CreateScope())
{
????var?dbContext?=?scope.ServiceProvider.GetRequiredService();
????dbContext.Posts.Add(new?Post()?{?Id?=?1,?Author?=?"test",?Title?=?"test",?PostedAt?=?DateTime.UtcNow?});
????dbContext.SaveChanges();
????var?post?=?dbContext.Posts.Find(1);
????post.Author?=?"test2";
????dbContext.SaveChanges();
????dbContext.Posts.Remove(post);
????dbContext.SaveChanges();
}
輸出結(jié)果(輸出結(jié)果的如果數(shù)據(jù)為 null 就會(huì)被忽略掉,所以對(duì)于新增的數(shù)據(jù)實(shí)際是沒有原始值的,對(duì)于刪除的數(shù)據(jù)沒有新的值):

More
EF Core 5 還有很多新的特性,有需要的小伙伴可以看一下官方文檔的介紹~
上述源碼可以在 Github 上獲取 https://github.com/WeihanLi/SamplesInPractice/blob/master/EF5Samples/SaveChangesInterceptorTest.cs
Reference
https://github.com/dotnet/efcore https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#savechanges-interception-and-events https://www.cnblogs.com/weihanli/p/13416219.html https://github.com/WeihanLi/SamplesInPractice/blob/master/EF5Samples/SaveChangesInterceptorTest.cs
