EFCore 5 新特性 SaveChangesInterceptor

Intro

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

SaveChangesInterceptor

源码实现:

public interface ISaveChangesInterceptor : IInterceptor
{
/// <summary>
/// Called at the start of <see cref="M:DbContext.SaveChanges" />.
/// </summary>
/// <param name="eventData"> Contextual information about the <see cref="DbContext" /> being used. </param>
/// <param name="result">
/// Represents the current result if one exists.
/// This value will have <see cref="InterceptionResult{Int32}.HasResult" /> set to <see langword="true" /> if some previous
/// interceptor suppressed execution by calling <see cref="InterceptionResult{Int32}.SuppressWithResult" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <returns>
/// If <see cref="InterceptionResult{Int32}.HasResult" /> is false, the EF will continue as normal.
/// If <see cref="InterceptionResult{Int32}.HasResult" /> is true, then EF will suppress the operation it
/// was about to perform and use <see cref="InterceptionResult{Int32}.Result" /> instead.
/// A normal implementation of this method for any interceptor that is not attempting to change the result
/// is to return the <paramref name="result" /> value passed in.
/// </returns>
InterceptionResult<int> SavingChanges(
[NotNull] DbContextEventData eventData,
InterceptionResult<int> result); /// <summary>
/// <para>
/// Called at the end of <see cref="M:DbContext.SaveChanges" />.
/// </para>
/// <para>
/// This method is still called if an interceptor suppressed creation of a command in <see cref="SavingChanges" />.
/// In this case, <paramref name="result" /> is the result returned by <see cref="SavingChanges" />.
/// </para>
/// </summary>
/// <param name="eventData"> Contextual information about the <see cref="DbContext" /> being used. </param>
/// <param name="result">
/// The result of the call to <see cref="M:DbContext.SaveChanges" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <returns>
/// 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 <paramref name="result" /> value passed in.
/// </returns>
int SavedChanges(
[NotNull] SaveChangesCompletedEventData eventData,
int result); /// <summary>
/// Called when an exception has been thrown in <see cref="M:DbContext.SaveChanges" />.
/// </summary>
/// <param name="eventData"> Contextual information about the failure. </param>
void SaveChangesFailed(
[NotNull] DbContextErrorEventData eventData); /// <summary>
/// Called at the start of <see cref="M:DbContext.SaveChangesAsync" />.
/// </summary>
/// <param name="eventData"> Contextual information about the <see cref="DbContext" /> being used. </param>
/// <param name="result">
/// Represents the current result if one exists.
/// This value will have <see cref="InterceptionResult{Int32}.HasResult" /> set to <see langword="true" /> if some previous
/// interceptor suppressed execution by calling <see cref="InterceptionResult{Int32}.SuppressWithResult" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <param name="cancellationToken"> The cancellation token. </param>
/// <returns>
/// If <see cref="InterceptionResult{Int32}.HasResult" /> is false, the EF will continue as normal.
/// If <see cref="InterceptionResult{Int32}.HasResult" /> is true, then EF will suppress the operation it
/// was about to perform and use <see cref="InterceptionResult{Int32}.Result" /> instead.
/// A normal implementation of this method for any interceptor that is not attempting to change the result
/// is to return the <paramref name="result" /> value passed in.
/// </returns>
ValueTask<InterceptionResult<int>> SavingChangesAsync(
[NotNull] DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default); /// <summary>
/// <para>
/// Called at the end of <see cref="M:DbContext.SaveChangesAsync" />.
/// </para>
/// <para>
/// This method is still called if an interceptor suppressed creation of a command in <see cref="SavingChangesAsync" />.
/// In this case, <paramref name="result" /> is the result returned by <see cref="SavingChangesAsync" />.
/// </para>
/// </summary>
/// <param name="eventData"> Contextual information about the <see cref="DbContext" /> being used. </param>
/// <param name="result">
/// The result of the call to <see cref="M:DbContext.SaveChangesAsync" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <param name="cancellationToken"> The cancellation token. </param>
/// <returns>
/// 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 <paramref name="result" /> value passed in.
/// </returns>
ValueTask<int> SavedChangesAsync(
[NotNull] SaveChangesCompletedEventData eventData,
int result,
CancellationToken cancellationToken = default); /// <summary>
/// Called when an exception has been thrown in <see cref="M:DbContext.SaveChangesAsync" />.
/// </summary>
/// <param name="eventData"> Contextual information about the failure. </param>
/// <param name="cancellationToken"> The cancellation token. </param>
/// <returns> A <see cref="Task" /> representing the asynchronous operation. </returns>
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<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
var changesList = new List<CompareModel>(); foreach (var entry in
eventData.Context.ChangeTracker.Entries<Post>())
{
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; }
}
}

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

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

public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> dbContextOptions) : base(dbContextOptions)
{
} public DbSet<Post> 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<TestDbContext>(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<TestDbContext>();
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

EFCore 5 新特性 `SaveChangesInterceptor`的更多相关文章

  1. EFCore 5 新特性 —— Savepoints

    EFCore 5 中的 Savepoints Intro EFCore 5中引入了一个新特性,叫做 Savepoints,主要是事务中使用,个人感觉有点类似于 Windows 上的系统还原点,如果事务 ...

  2. efcore 新特性 SaveChanges Events

    efcore 新特性 SaveChanges Events Intro 昨天早上看到之前关注的一个 efcore 的 issue 被 closed ,于是看了一眼, ef core 新合并了一个 PR ...

  3. SQL Server 2014 新特性——内存数据库

    SQL Server 2014 新特性——内存数据库 目录 SQL Server 2014 新特性——内存数据库 简介: 设计目的和原因: 专业名词 In-Memory OLTP不同之处 内存优化表 ...

  4. ElasticSearch 5学习(10)——结构化查询(包括新特性)

    之前我们所有的查询都属于命令行查询,但是不利于复杂的查询,而且一般在项目开发中不使用命令行查询方式,只有在调试测试时使用简单命令行查询,但是,如果想要善用搜索,我们必须使用请求体查询(request ...

  5. [干货来袭]C#6.0新特性

    微软昨天发布了新的VS 2015 ..随之而来的还有很多很多东西... .NET新版本 ASP.NET新版本...等等..太多..实在没消化.. 分享一下也是昨天发布的新的C#6.0的部分新特性吧.. ...

  6. CSS3新特性应用之结构与布局

    一.自适应内部元素 利用width的新特性min-content实现 width新特性值介绍: fill-available,自动填充盒子模型中剩余的宽度,包含margin.padding.borde ...

  7. 【译】Meteor 新手教程:在排行榜上添加新特性

    原文:http://danneu.com/posts/6-meteor-tutorial-for-fellow-noobs-adding-features-to-the-leaderboard-dem ...

  8. 跨平台的 .NET 运行环境 Mono 3.2 新特性

    Mono 3.2 发布了,对 Mono 3.0 和 2.10 版本的支持不再继续,而且这两个分支也不再提供 bug 修复更新. Mono 3.2 主要新特性: LLVM 更新到 3.2 版本,带来更多 ...

  9. Atitit opencv版本新特性attilax总结

    Atitit opencv版本新特性attilax总结 1.1. :OpenCV 3.0 发布,史上功能最全,速度最快的版1 1.2. 应用领域2 1.3. OPENCV2.4.3改进 2.4.2就有 ...

随机推荐

  1. SCOI 2008 【奖励关】

    早上的考试一道都做不出,被教做人,心态爆炸ing...... 题目描述: 你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关.在这个奖励关里,系统将依次随机抛出k次宝物,每次你都可以选择吃或者不吃(必 ...

  2. 使用css实现轮播图

    使用css3实现图片轮播 前言:实现图片轮播的方式有很多种 ,例如js ,css 等等. 本文主要讲述使用纯css3实现轮播图 工具介绍: 使用的编辑器: Hbuilder 进入正题 html代码: ...

  3. Oracle和MySql之间SQL区别(等效转换以及需要注意的问题)

    本篇博文是Oracle和MySQL之间的等效SQL转换和不同,目前市面上没有转换两种SQL的工具,小编觉得以后也不一定会有,于是在业余时间整理了一下,如果有什么错误之处请留言告知,小编也是刚入门的小白 ...

  4. pytest框架: fixture之conftest.py

    原文地址:https://blog.csdn.net/BearStarX/article/details/101000516 一.fixture优势1.fixture相对于setup和teardown ...

  5. 【贪心算法】HDU 5747 Aaronson

    题目大意 vjudge链接 给你一个n,m,求解满足等式x0+2x1+4x2+...+2mxm=n的x0~xm的最小和(xi为非负整数) 数据范围 0≤n,m≤109 思路 n和m都在int范围内,所 ...

  6. jmeter_02_目录文档说明

    jmeter目录文档说明 bin目录是可执行文件 jmeter.bat 是启动文件 可以启动jmeter. 使用notpad++ 等文本编辑器打开 bat文件 可以配置jvm的参数 比如堆内存[Hea ...

  7. MySQL锁详细讲解

    本文章向大家介绍MySQL锁详细讲解,包括数据库锁基本知识.表锁.表读锁.表写锁.行锁.MVCC.事务的隔离级别.悲观锁.乐观锁.间隙锁GAP.死锁等等,需要的朋友可以参考一下   锁的相关知识又跟存 ...

  8. flink 处理实时数据的三重保障

    flink 处理实时数据的三重保障 window+watermark 来处理乱序数据对于 TumblingEventTimeWindows window 的元数据startTime,endTime 和 ...

  9. Python之for循环和列表

    for循环: 有限循环 基本语法: for 变量 in 可迭代对象: 循环体 也可使用break,continue,for else list列表初识: 列表可放任意数据类型:[int,str,boo ...

  10. HTML编辑器(1)

    前言 现在网上有很多这样的HTML编辑器,这种编辑器无疑给人带来了很多方便,所以自己也想尝试制作一款这样的HTML编辑器,既然要制作,那就肯定是先把UI搭起来,再慢慢完善功能 设计思路 我的思路就是将 ...