Skip to content

Storage Providers

RzR edited this page Apr 20, 2026 · 1 revision

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.


SQL Server

Packages

<PackageReference Include="RzR.DataVigil.EFCore" />
<PackageReference Include="RzR.DataVigil.Storage.EfSqlServer" />

Setup

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 exist

What gets created

Three 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.

Implementation details

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.


PostgreSQL

Packages

<PackageReference Include="RzR.DataVigil.EFCore" />
<PackageReference Include="RzR.DataVigil.Storage.EfPostgreSql" />

Setup

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).


MongoDB

Packages

<PackageReference Include="RzR.DataVigil.EFCore" />
<PackageReference Include="RzR.DataVigil.Storage.EfMongoDb" />

Note: the MongoDB package targets net8.0 (not netstandard2.1) because MongoDB.EntityFrameworkCore requires it.

Setup

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 write

Data model

MongoDB 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.

Read auditing with MongoDB

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();

File Storage

Packages

<PackageReference Include="RzR.DataVigil.Storage.File" />

No EFCore package needed - file storage talks directly to the filesystem.

Setup

builder.Services.AddAuditTrail(options =>
{
    options.EfCore.Intercept<AppDbContext>();
    options.Storage.UseFile(
        Path.Combine(Directory.GetCurrentDirectory(), "audit-logs"));
});

builder.Services.AddAuditTrailEfCore();
builder.Services.AddAuditTrailFileStorage();

How it stores data

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.

Querying

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.

Right-to-erasure and purge

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.


Comparison table

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

Clone this wiki locally