EF Core – QueryFilter & Interception
主要参考
QueryFilter
QueryFilter 就是默认过滤, 非常适合用来做 Soft Delete
builder.HasQueryFilter(e => EF.Property<DateTimeOffset?>(e, "DateDeleted") == null);
设置这个以后, 一般的 query 语句就拿不到 deleted 的 row 了
如果想获取 deleted row 那么就需要通过 IgnoreQueryFilters 来 by pass 它.
blogs = db.Blogs .Include(b => b.Posts) .IgnoreQueryFilters() .ToList();
Interception
Interception 也适合用来做 Soft Delete 或者简单的 Audit Trail
但是还有一个更简单的做法是直接 override SaveChangesAsync 方法
去 DbContext class
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { foreach (var entry in ChangeTracker.Entries()) { if (entry.State == EntityState.Added) { } else if (entry.State == EntityState.Modified) { } else if (entry.State == EntityState.Deleted) { } var tableName = entry.Metadata.GetTableName(); foreach (var property in entry.Properties) { var isModified = property.IsModified; var originalValue = property.OriginalValue; var currentValue = property.CurrentValue; var metadata = property.Metadata; } } return base.SaveChangesAsync(cancellationToken); }
获取 Entry 资料, 然后修改 Entry 就可以操控最终 save 的结构了. (比如把 Deleted 换成 Modified)
创建一个 Interceptor (我这里用 SaveChangesInterceptor 举例)
public class SoftDeleteInterception : SaveChangesInterceptor { public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { var entities = eventData.Context!.ChangeTracker.Entries(); return new ValueTask<InterceptionResult<int>>(result); } }
注: SavingChangesAsync 是 before SQL, SavedChangesAsync 是 after success SQL, FailedChangesAsync 是 after fail SQL.
在 DbContext class register 这个 interceptor
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.AddInterceptors(new SoftDeleteInterception());
Change State in SaveChanges
看注释, 有些逻辑和平时会不同,比如 set value 时, 不会 update IsModified (但 generate 出来的语句还是有的, 我想可能 EF 最后还会再对比一次吧...懒惰去研究了)
foreach (var entry in eventData.Context!.ChangeTracker.Entries()) { var entityType = entry.Entity.GetType(); if (entityType.FullName == "TestEFCore.Product") { entry.State = EntityState.Unchanged; // if change to modified then all properties will become IsModified typeof(Product).GetProperty("Name")!.SetValue(entry.Entity, "New Value"); // current value 会 update, but IsModified 依然是 false entry.Property("DateDeleted").CurrentValue = DateTimeOffset.Now; entry.Property("DateDeleted").IsModified = true; // will update entry.State to modified foreach (var p in entry.Properties) { var isModified = p.IsModified; } var state = entry.State; } }
Dependency Injection inside Interceptor
参考:
Ability to register IInterceptor without an IDbContextOptionsExtension
A better way of resolving EF Core interceptors with dependency injection
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期