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.