History lives in your migrations
Mark an entity with IsTemporal() and the next dotnet ef migrations add creates its history table and indexes. No database extensions, no superuser, no hand-written DDL.
Every change to an entity kept as a version, queryable with LINQ, created by your migrations, stamped with who and why.
// Make an entity temporal; the next migration adds its history table.
modelBuilder.Entity<Policy>().IsTemporal();
// What did this policy look like when the claim happened?
var policy = await db.Policies
.AsOf(claim.OccurredAt)
.SingleAsync(p => p.Id == claim.PolicyId);
// Who changed it, when, and why?
var audit = await db.History<Policy>()
.Where(v => v.Entity.Id == claim.PolicyId)
.Select(v => new { v.ValidFrom, v.ChangedBy, v.Reason, v.Entity.Status, v.Entity.Premium })
.ToListAsync();Requires .NET 10, EF Core 10 with the Npgsql provider, and PostgreSQL 14 or later.