-
Notifications
You must be signed in to change notification settings - Fork 0
Storage Providers
DataVigil ships with four storage backends. Pick the one that fits your infrastructure. They all implement IAuditStore and are interchangeable - the rest of your code doesn't know or care which one you're using.
<PackageReference Include="RzR.DataVigil.EFCore" />
<PackageReference Include="RzR.DataVigil.Storage.EfSqlServer" />builder.Services.AddAuditTrail(options =>
{
options.EfCore.Intercept<AppDbContext>();
options.Storage.UseSqlServer(
builder.Configuration.GetConnectionString("AuditDb"));
options.Storage.Schema = "audit"; // default is "audit"
});
builder.Services.AddAuditTrailEfCore();
builder.Services.AddAuditTrailSqlServer();
var app = builder.Build();
app.Services.MigrateAuditSqlServerDb(); // creates tables if they don't existThree tables under the configured schema:
| Table | Columns | Indexes |
|---|---|---|
audit.AuditTransactions |
Id (PK), Timestamp, UserId, UserName, IpAddress, CorrelationId, TraceId, Source, GdprState, Metadata (JSON) | Timestamp, UserId, CorrelationId |
audit.AuditEntries |
Id (PK), TransactionId (FK), Action, EntityName, EntityId, EntityTypeName | TransactionId, EntityName |
audit.AuditEntryProperties |
Id (PK, auto-generated), AuditEntryId (FK), PropertyName, PropertyType, OldValue, NewValue | AuditEntryId |
Cascade delete is configured: removing a transaction removes its entries and properties.
SqlServerAuditStore uses AuditSqlServerDbContext (inherits AuditDbContextBase) for all operations. Queries use AsNoTracking() for performance and include Entries -> Properties via eager loading. The right-to-erasure call (AnonymizeByUserAsync) loads matching transactions, replaces UserId, UserName, IpAddress with [ERASED], and sets GdprState = Erased.
<PackageReference Include="RzR.DataVigil.EFCore" />
<PackageReference Include="RzR.DataVigil.Storage.EfPostgreSql" />builder.Services.AddAuditTrail(options =>
{
options.EfCore.Intercept<AppDbContext>();
options.Storage.UsePostgreSql(
builder.Configuration.GetConnectionString("AuditDb"));
});
builder.Services.AddAuditTrailEfCore();
builder.Services.AddAuditTrailPostgreSqlServer();
var app = builder.Build();
app.Services.MigrateAuditPostgreSqlDb();The table structure and behavior are identical to SQL Server. The only difference is the underlying EF Core provider (Npgsql).
<PackageReference Include="RzR.DataVigil.EFCore" />
<PackageReference Include="RzR.DataVigil.Storage.EfMongoDb" />Note: the MongoDB package targets net8.0 (not netstandard2.1) because
MongoDB.EntityFrameworkCorerequires it.
builder.Services.AddAuditTrail(options =>
{
options.EfCore.Intercept<AppDbContext>();
options.Storage.UseMongoDb(
builder.Configuration.GetConnectionString("AuditDb"),
builder.Configuration["DatabaseNames:AuditDb"]);
});
builder.Services.AddAuditTrailEfCore();
builder.Services.AddAuditTrailMongoDb();
// No migration step needed - MongoDB creates collections on first writeMongoDB uses a document model instead of relational tables. Each AuditTransaction is a single document in the audit_transactions collection, with Entries and their Properties embedded as nested arrays. No joins, no foreign keys.
The AuditMongoDbContext uses OwnsMany to map entries and properties as owned types within the transaction document.
MongoDB doesn't use DbCommand, so the relational AuditCommandInterceptor won't fire. Instead, MongoDB uses AuditMaterializationInterceptor:
builder.Services.AddDbContext<AppDbContext>((sp, opts) =>
{
opts.UseMongoDB(connectionString, databaseName);
opts.AddAuditInterceptors(sp); // SaveChanges interceptor
opts.AddAuditReadInterceptor(sp); // Materialization interceptor
});And don't forget the flush middleware:
app.UseAuditReadFlush();<PackageReference Include="RzR.DataVigil.Storage.File" />No EFCore package needed - file storage talks directly to the filesystem.
builder.Services.AddAuditTrail(options =>
{
options.EfCore.Intercept<AppDbContext>();
options.Storage.UseFile(
Path.Combine(Directory.GetCurrentDirectory(), "audit-logs"));
});
builder.Services.AddAuditTrailEfCore();
builder.Services.AddAuditTrailFileStorage();One JSON file per day: audit-2026-04-16.json. Each file contains a JSON array of AuditTransaction objects. The directory is created automatically if it doesn't exist.
Writes are thread-safe (protected by lock). The store reads the current file, deserializes, appends the new transaction, and rewrites the file. For high-throughput scenarios, consider one of the database-backed providers instead.
QueryAsync reads all audit-*.json files in the directory, deserializes them, applies GDPR retrieval policies, and returns paginated results. There's no indexing - it's a linear scan. Fine for dev/testing or low-volume scenarios, not ideal for production systems with millions of records.
Both operations work by reading all files, filtering/transforming in memory, and rewriting the files. The purge operation removes transactions older than the cutoff date and rewrites each affected file.
| Feature | SQL Server | PostgreSQL | MongoDB | File |
|---|---|---|---|---|
| Target framework | netstandard2.1 | netstandard2.1 | net8.0 | netstandard2.1 |
| Migrations required | Yes | Yes | No | No |
| Schema support | Yes | Yes | N/A | N/A |
| Read audit mechanism | AuditCommandInterceptor |
AuditCommandInterceptor |
AuditMaterializationInterceptor |
N/A |
| Indexing | Database indexes | Database indexes | MongoDB indexes | None (linear scan) |
| Thread safety | EF Core handles it | EF Core handles it | EF Core handles it |
lock on writes |
| Good for production | Yes | Yes | Yes | Low-volume only |
| DI registration | AddAuditTrailSqlServer() |
AddAuditTrailPostgreSqlServer() |
AddAuditTrailMongoDb() |
AddAuditTrailFileStorage() |
| Migration call | MigrateAuditSqlServerDb() |
MigrateAuditPostgreSqlDb() |
N/A | N/A |
RzR.DataVigil · Source Code · NuGet Packages · Built with .NET Standard 2.1
Getting started
Core features
Reference
Resources