Skip to content

Data Model

RzR edited this page Apr 20, 2026 · 1 revision

Data Model

This page documents the audit data model — the objects that get persisted and the database schema they map to.


Object model

AuditTransaction

The top-level record. One transaction per SaveChanges call (or per manual pipeline push).

Property Type Description
Id Guid Unique identifier
Timestamp DateTimeOffset When the operation happened (UTC)
UserId string Who performed the action
UserName string Display name of the user
IpAddress string Client IP address
CorrelationId string Request correlation ID for distributed tracing
TraceId string Distributed trace ID
Source string Application or service name
GdprState GdprStorageState GDPR processing state of this transaction
Metadata IDictionary<string, string> Custom key-value pairs (serialized as JSON)
Entries ICollection<AuditEntry> The individual entity changes in this transaction

AuditEntry

One entry per entity that was changed (or read) within the transaction.

Property Type Description
Id Guid Unique identifier
TransactionId Guid FK to parent transaction
Action AuditAction Create (1), Read (2), Update (3), Delete (4)
EntityName string Entity class name (e.g., "Order")
EntityId string Primary key value(s). Composite keys joined with _
EntityTypeName string Full CLR type name
Properties ICollection<AuditEntryProperty> Property-level changes

AuditEntryProperty

One property per field that changed (or was captured for reads).

Property Type Description
PropertyName string Property/column name
PropertyType string CLR type name (e.g., "System.String")
OldValue string Previous value (null for Create)
NewValue string New value (null for Delete)

Database schema (SQL Server / PostgreSQL)

All tables use the configured schema (default: audit).

audit.AuditTransactions

CREATE TABLE [audit].[AuditTransactions] (
    [Id]            UNIQUEIDENTIFIER   NOT NULL  PRIMARY KEY,
    [Timestamp]     DATETIMEOFFSET     NOT NULL,
    [UserId]        NVARCHAR(MAX)      NULL,
    [UserName]      NVARCHAR(MAX)      NULL,
    [IpAddress]     NVARCHAR(MAX)      NULL,
    [CorrelationId] NVARCHAR(MAX)      NULL,
    [TraceId]       NVARCHAR(MAX)      NULL,
    [Source]        NVARCHAR(MAX)      NULL,
    [GdprState]     INT                NOT NULL  DEFAULT 0,
    [Metadata]      NVARCHAR(MAX)      NULL      -- JSON
);

CREATE INDEX [IX_AuditTransactions_Timestamp]     ON [audit].[AuditTransactions] ([Timestamp]);
CREATE INDEX [IX_AuditTransactions_UserId]        ON [audit].[AuditTransactions] ([UserId]);
CREATE INDEX [IX_AuditTransactions_CorrelationId] ON [audit].[AuditTransactions] ([CorrelationId]);

audit.AuditEntries

CREATE TABLE [audit].[AuditEntries] (
    [Id]             UNIQUEIDENTIFIER   NOT NULL  PRIMARY KEY,
    [TransactionId]  UNIQUEIDENTIFIER   NOT NULL,
    [Action]         INT                NOT NULL,
    [EntityName]     NVARCHAR(MAX)      NULL,
    [EntityId]       NVARCHAR(MAX)      NULL,
    [EntityTypeName] NVARCHAR(MAX)      NULL,

    CONSTRAINT [FK_AuditEntries_AuditTransactions_TransactionId]
        FOREIGN KEY ([TransactionId])
        REFERENCES [audit].[AuditTransactions] ([Id])
        ON DELETE CASCADE
);

CREATE INDEX [IX_AuditEntries_TransactionId] ON [audit].[AuditEntries] ([TransactionId]);
CREATE INDEX [IX_AuditEntries_EntityName]    ON [audit].[AuditEntries] ([EntityName]);

audit.AuditEntryProperties

CREATE TABLE [audit].[AuditEntryProperties] (
    [Id]            UNIQUEIDENTIFIER   NOT NULL  PRIMARY KEY  DEFAULT NEWSEQUENTIALID(),
    [AuditEntryId]  UNIQUEIDENTIFIER   NOT NULL,
    [PropertyName]  NVARCHAR(MAX)      NULL,
    [PropertyType]  NVARCHAR(MAX)      NULL,
    [OldValue]      NVARCHAR(MAX)      NULL,
    [NewValue]      NVARCHAR(MAX)      NULL,

    CONSTRAINT [FK_AuditEntryProperties_AuditEntries_AuditEntryId]
        FOREIGN KEY ([AuditEntryId])
        REFERENCES [audit].[AuditEntries] ([Id])
        ON DELETE CASCADE
);

CREATE INDEX [IX_AuditEntryProperties_AuditEntryId] ON [audit].[AuditEntryProperties] ([AuditEntryId]);

Cascade deletes

Deleting an AuditTransaction cascades to its AuditEntries, and deleting an AuditEntry cascades to its AuditEntryProperties. This means PurgeBeforeAsync only needs to delete transactions - entries and properties go with them.


MongoDB document structure

In MongoDB, each AuditTransaction is a single document in the audit_transactions collection. Entries and properties are embedded arrays:

{
  "_id": "550e8400-e29b-41d4-a716-446655440000",
  "Timestamp": "2026-04-16T10:30:00+00:00",
  "UserId": "user-42",
  "UserName": "Alice",
  "IpAddress": "192.168.1.10",
  "CorrelationId": "abc-123",
  "TraceId": "def-456",
  "Source": "OrderService",
  "GdprState": 0,
  "Metadata": { "RequestPath": "/api/orders" },
  "Entries": [
    {
      "Id": "660e8400-e29b-41d4-a716-446655440001",
      "Action": 3,
      "EntityName": "Order",
      "EntityId": "42",
      "EntityTypeName": "MyApp.Entities.Order",
      "Properties": [
        {
          "PropertyName": "Status",
          "PropertyType": "System.String",
          "OldValue": "Pending",
          "NewValue": "Shipped"
        }
      ]
    }
  ]
}

No joins are needed - the entire audit trail for one operation is in a single document.


File storage format

Each daily JSON file (audit-2026-04-16.json) contains a JSON array of transaction objects using the same structure as the object model above. The format is identical to what System.Text.Json serializes from AuditTransaction.

Clone this wiki locally