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

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Blog — PlanetScale
Blog — PlanetScale
Jina AI
Jina AI
MyScale Blog
MyScale Blog
N
Netflix TechBlog - Medium
月光博客
月光博客
云风的 BLOG
云风的 BLOG
T
The Blog of Author Tim Ferriss
博客园_首页
GbyAI
GbyAI
The Cloudflare Blog
博客园 - 叶小钗
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
MongoDB | Blog
MongoDB | Blog
Y
Y Combinator Blog
博客园 - 三生石上(FineUI控件)
量子位
博客园 - Franky
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
M
MIT News - Artificial intelligence
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
I
InfoQ
Google DeepMind News
Google DeepMind News
S
SegmentFault 最新的问题
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
J
Java Code Geeks
腾讯CDC
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
S
Schneier on Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
Lohrmann on Cybersecurity
S
Securelist
F
Full Disclosure
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
The GitHub Blog
The GitHub Blog

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 Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Stop Hand-Rolling Audit Logs in EF Core
RzR · 2026-06-15 · via DEV Community

Every team that touches regulated data eventually has the same meeting. Someone from compliance asks a deceptively simple question: who changed this customer's email address, when, and what was it before? And the room goes quiet, because the honest answer is that nobody knows. The change happened three sprints ago, the previous value is gone, and the closest thing to a record is a ModifiedBy column that says "system."

If you have lived through that meeting, the rest of this will feel familiar. And if you are the manager who has to explain to the auditor, or to legal, or to a customer why the answer is "we'll have to look into it," it probably stung more.

This post is written for both seats at that table. There is enough detail here for the developer who has to wire it up, and enough framing for the engineering manager who has to decide whether it is worth the team's time. Skip ahead if a section is not yours; the code blocks are for the people writing code, and the build-versus-buy math is for the people approving it.

Most .NET teams solve auditing the slow way. They sprinkle logging calls through their service layer, add CreatedBy and UpdatedBy columns to every table, maybe write a base repository that tries to diff entities by hand. It works until it doesn't. Someone adds a new entity and forgets the audit hook. A bulk update bypasses the service layer entirely. The diff logic captures the new value but not the old one. Six months in, you have audit coverage that is roughly 70 percent complete and 100 percent untrustworthy, which is worse than none at all because it gives you false confidence.

RzR.DataVigil takes a different bet. Instead of asking you to remember to audit, it hooks into the one place every change has to pass through anyway: EF Core's SaveChanges.

The interceptor does the watching

Here is the core idea. EF Core has an interceptor pipeline, and DataVigil plugs into it with AuditSaveChangesInterceptor. When SavingChanges fires, it walks the ChangeTracker, picks out every Added, Modified, or Deleted entry that implements IAuditable, and builds a record of exactly what changed at the property level. Old value and new value, side by side, for every field that moved.

Your entities do not change shape. You do not write diff code. You add a marker interface and the watching happens underneath you.

public class Order : IAuditable
{
    public int Id { get; set; }
    public string CustomerEmail { get; set; }
    public decimal TotalAmount { get; set; }
    public string Status { get; set; }
}

That is the whole entity-side change. From that point on, every create, update, and delete on Order produces an audit transaction. Entities that do not implement IAuditable are ignored completely. The interceptor does not even look at them, so there is no performance tax on the parts of your model you do not care about.

The shape of what gets captured is worth pausing on, because it is more honest than most homegrown systems. A create records null as the old value and the new value as new. An update records only the properties that actually changed, with their previous and current values. A delete records the previous values against null. No noise, no rows full of unchanged fields pretending to be changes.

Who, not just what

Capturing the change is half the job. The half that compliance actually cares about is attribution, and this is where most roll-your-own systems fall apart.

DataVigil enriches every transaction with context. In an ASP.NET Core app, you call AddAuditTrailAspNetCore() and it pulls the user identity straight from HttpContext: the NameIdentifier or sub claim for the user ID, the Name claim for the username, the remote IP address. It looks for an X-Correlation-Id or X-Request-Id header and falls back to the current Activity if neither exists, so the audit record ties back to the exact request that caused it. Trace IDs come along for the ride. You get a record that can answer "who changed what, when, and as part of which request" without you wiring a single thing into your controllers.

And before you ask: yes, it works outside the web. Background workers and console apps have no HttpContext, so you drop the ASP.NET Core package and set the user yourself through IAuditScopeContext.

scopeContext.SetUser(new AuditUserInfo
{
    UserId = "worker-1",
    UserName = "BackgroundWorker",
    IpAddress = "127.0.0.1"
});

Now your nightly batch job writes audit records attributed to worker-1 instead of a shrug. That distinction matters more than it sounds. The first time an auditor asks why a thousand records changed at 3 a.m., "the reconciliation worker did it, here is the run" is a much better answer than silence.

GDPR is built in, not bolted on

This is the part that earns the library its place in a regulated codebase, and it is genuinely the strongest thing about it.

Auditing and privacy law are in natural tension. You want to record everything that changed, but you are not allowed to keep a plaintext copy of someone's Social Security number sitting in an audit table forever just because it appeared in an update. DataVigil resolves this with field-level policies that run before anything reaches the database.

options.Gdpr.ForEntity<Customer>(e =>
{
    e.ExcludeOnStorage(c => c.CreditCardNumber);   // never written
    e.MaskOnStorage(c => c.Email);                 // j***n@mail.com
    e.HashOnStorage(c => c.Ssn);                   // SHA-256 digest
    e.AnonymizeOnStorage(c => c.Phone);            // [ANONYMIZED]
});

You decide, per field, whether a value is excluded entirely, masked, hashed, anonymized, or run through a transform you write yourself. The credit card number never lands in the audit store. The email is masked. The SSN is hashed, so you can still prove two records referenced the same value without ever storing the value.

Then there is a second, separate layer for retrieval. Storing data safely is one thing; controlling who can read it back is another. DataVigil lets you gate fields by role or claim at query time.

options.Gdpr.ForEntity<Customer>(e =>
{
    e.MaskOnRetrieval(c => c.Email, access => access
        .AllowRoles("Admin", "Auditor"));

    e.AnonymizeOnRetrieval(c => c.Ssn, access => access
        .AllowClaim("gdpr", "full"));
});

A support tier-one user querying the audit trail sees masked emails. An auditor with the right role sees them in full. The same stored record, two different views, decided by who is asking. Roles are checked first, then claims, and a field with no match stays masked. You do not build this. You declare it.

The piece that will make a compliance officer actually smile is the right-to-erasure support. GDPR Article 17 says a person can demand their personal data be wiped. With most audit systems that is a contradiction, because the whole point of an audit log is that you do not delete from it. DataVigil threads the needle:

await auditStore.AnonymizeByUserAsync("user-123");

That call scrubs the user's identity: UserId, UserName, IpAddress, across every transaction they ever touched, replacing them with [ERASED], while leaving the structural audit records intact. You satisfy the erasure request and keep a defensible audit trail. Those two goals usually fight each other. Here they do not.

The cost nobody puts on the sprint board

Here is the part for whoever owns the roadmap.

A homegrown audit system rarely shows up as a line item. It arrives as scope creep on a dozen other tickets. A developer adds two hours of audit plumbing to a feature, then another two when the requirements shift, then a day six months later debugging why the trail went silent after a refactor. None of it is tracked as "audit work," so it never gets estimated, and it never gets credited. It just quietly eats velocity, and the bill comes due during a compliance review when you discover the coverage has holes and someone has to spend a sprint backfilling.

The build-versus-buy math here is not close. The capability you would spend weeks building and then maintaining indefinitely: interceptor wiring, property diffing, user attribution, field-level redaction, retrieval gating, erasure, retention, already exists, tested, behind a marker interface and a few lines of registration. The maintenance cost moves off your team and onto the library. When EF Core 10 lands, that is the maintainer's problem to keep current, not a backlog item that competes with your roadmap.

There is a risk angle too, and it is the one that should get a manager's attention. An incomplete audit trail is a liability dressed up as a feature. It tells the organization "we have auditing" while quietly failing to record the exact events that matter most, which is precisely the situation that turns a routine GDPR inquiry into a bad week. Pushing the audit logic down to the interceptor removes the human step where coverage gets forgotten. That is not a convenience. It is the difference between a control you can attest to and one you are hoping holds.

And the GDPR features above are not just developer ergonomics. Field-level masking, hashing, role-gated retrieval, and one-call right-to-erasure are the concrete artifacts you point to when someone asks how you handle personal data in your logs. They turn an abstract policy commitment into something a developer can show working in code, which is exactly what an auditor wants to see and exactly what is hardest to produce after the fact.

Store it wherever you already live

None of this forces a database on you. DataVigil ships four storage providers behind a single IAuditStore interface: SQL Server, PostgreSQL, and MongoDB for the real backends, plus flat JSON files (one per day) for when you just need something quick or local. The SQL providers create their own schema with sensible indexes on timestamp, user, and correlation ID, and you point them at a separate database so a careless migration on your app data cannot take the audit trail down with it.

If none of the four fit, you implement IAuditStore yourself. Four methods: save, query, anonymize by user, purge before a date. That is the entire contract.

That last method, purge, hooks into a built-in retention service. Set a window, register the background service, and old records get cleaned up on a daily cycle without a cron job you have to babysit.

options.Storage.WithRetention(90); // purge entries older than 90 days
services.AddAuditRetentionService();

What it actually buys you

The honest pitch is not that DataVigil does something you could never do yourself. You could build all of this. The pitch is that the version you build yourself will have gaps, and the gaps are exactly where the risk lives: the entity someone forgot to hook, the bulk operation that skipped the service layer, the SSN that got logged in plaintext because nobody thought about that field.

Moving the audit logic down to the interceptor closes the gap by construction. There is no path to the database that does not pass through SaveChanges, so there is no path that escapes the audit. That structural guarantee is the whole thing. Everything else like the GDPR policies, the multi-store support, the erasure call, is what makes the guarantee livable in a codebase that has to answer to a regulator.

The library targets .NET Standard 2.1 and works with EF Core 5 through 9, so it slots into nearly anything you are running today. If you have a .NET app that holds data someone might one day ask questions about, the setup is about fifteen lines in Program.cs and a marker interface on your entities.

The next time compliance walks in and asks who changed what, you would rather have the answer ready than promise to go digging. Auditing is one of those things that is cheap to add before you need it and miserable to reconstruct after; which makes it a rare case where the developer's instinct and the manager's risk calculus point in the same direction.

The repo is here, and the wiki walks you from zero to a working trail in about five minutes. Fifteen lines in Program.cs for the team that builds it, one fewer open question for the person who has to answer for it.