惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
小众软件
小众软件
月光博客
月光博客
D
DataBreaches.Net
L
LangChain Blog
美团技术团队
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
博客园 - 司徒正美
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Help Net Security
阮一峰的网络日志
阮一峰的网络日志
Y
Y Combinator Blog
I
InfoQ
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
腾讯CDC
Martin Fowler
Martin Fowler

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I built an audit log for EF Core that can actually undo a...
Gamra-hub · 2026-06-07 · via DEV Community
Cover image for I built an audit log for EF Core that can actually undo a change

Gamra-hub

If you've shipped a few business apps you've probably written the same thing more than once: an audit log. A table that answers "who touched this row, when, and what did it look like before." It's never hard, exactly. It's just tedious, and you end up doing it again on the next project because the last one's version was tangled into that project's code.
The part that always bugged me more, though, is that the audit log just sits there. You have a perfect record of what changed, and when someone sets a price to 5 instead of 500, you still go fix it by hand in the database. All that history and you can't press undo.
So I finally wrote the version I wanted: capture the change and be able to reverse it. This is roughly how it works, and the library's at the end if you want it.
Capturing the changes
I went with a SaveChangesInterceptor. The appeal is that it lives at the DbContext level, so your entities stay clean — no base class, no IAuditable, no calls scattered through your services. The change tracker already knows everything that's about to be written; the interceptor just reads it.
If you're wondering why not a MediatR pipeline behaviour, which is the other common spot for this: MediatR went commercial last year, and a fair number of teams are now trying not to take a hard dependency on it. Keeping audit in the data layer sidesteps that entirely.
The shape is simple enough:
csharppublic override async ValueTask SavedChangesAsync(
SaveChangesCompletedEventData eventData, int result, CancellationToken ct = default)
{
var ctx = eventData.Context!;
foreach (var entry in ctx.ChangeTracker.Entries())
{
// record the action, the key, and the before/after values
// for anything added, modified, or deleted
}
return await base.SavedChangesAsync(eventData, result, ct);
}
The annoying details are the ones that don't show up in a snippet. An insert's key isn't known until after the save, so you can't grab it in SavingChanges — you read it afterwards. On an update you only want the properties that actually changed. And on a delete you have to keep the whole original row, not just the key, or you've got nothing to rebuild it from later.
Undo is the hard part
Capturing is the easy bit. Reversing is where it stops being uniform, because a delete and an update don't undo the same way. An update means putting the old values back. A delete means recreating the row. A create means deleting it. Fine so far.
Where it gets messy is that not everything reverses cleanly from a stored snapshot. Some rows have derived columns, or relationships, or rules that a blind overwrite would quietly break. I didn't want to pretend a snapshot is always safe, so the entity owner decides: by default it uses the snapshot, but you can register your own handler for the types that need real logic. The ones that are simple stay simple; the ones that aren't get an escape hatch.
In the end the calling code is just:
csharpawait reverter.RevertAsync(auditEntryId);
The library
I cleaned it up and put it on NuGet as EfCore.AuditKit. It's MIT, free, EF Core 10, and it doesn't drag in MediatR or anything commercial. Install is the usual dotnet add package EfCore.AuditKit, repo's here: https://github.com/Gamra-hub/AuditKit
I'll be honest about where it's at: it's a v1. It handles scalar properties and reverts one change at a time. The thing I'm working on next is reverting a whole multi-row operation as a unit, with a check so it refuses if someone's touched the data since.
If you've built one of these before, I'd actually want to hear where this falls down — especially the revert side, since that's the part I'm least sure I've got right for every case. Happy to be told I missed something.