首页 文章详情

EFCore 5 新特性 SaveChangesInterceptor

DotNet程序园 | 543 2020-11-19 15:44 0 0 0
UniSMS (合一短信)

EFCore 5 新特性 SaveChangesInterceptor

Intro

之前 EF Core 5 还没正式发布的时候有发布过一篇关于 SaveChangesEvents 的文章,有需要看可以移步到 efcore 新特性 SaveChanges Events,在后面的版本中又加入了 Interceptor 的支持,可以更方便的实现 SaveChanges 事件的复用, 今天主要介绍一下通过 SaveChangesInterceptor 来实现日志审计

SaveChangesInterceptor

源码实现:

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<intSavingChanges(
        [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<intSavedChangesAsync(
        [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
)
;
}

为了比较方便的实现自己需要的 Interceptor,微软还提供了一个 SaveChangesInterceptor 抽象类,这样只需要继承于这个类,重写自己需要的方法即可,实现比较简单,就是实现了 ISaveChangesInterceptor 接口,然后接口的实现基本都是空的虚方法,根据需要重写即可

源码链接:https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Diagnostics/SaveChangesInterceptor.cs

使用 SaveChangesInterceptor 实现自动审计

简单写了一个测试的审计拦截器

public class AuditInterceptor : SaveChangesInterceptor
{
    public override InterceptionResult<intSavingChanges(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 { getset; }

        public object NewValue { getset; }
    }
}

实际应用的话还需要根据自己的场景做一些修改和测试

测试 DbContext 示例,这里使用了一个简单的 InMemory 做了一个测试:

public class TestDbContext : DbContext
{
    public TestDbContext(DbContextOptions dbContextOptions) : base(dbContextOptions)
    {
    }

    public DbSet Posts { getset; }
}

public class Post
{
    [Key]
    public int Id { getset; }

    public string Author { getset; }

    public string Title { getset; }

    public DateTime PostedAt { getset; }
}

测试代码:

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();
}

输出结果(输出结果的如果数据为 null 就会被忽略掉,所以对于新增的数据实际是没有原始值的,对于删除的数据没有新的值):

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


good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter