Skip to content

Temporal entities ​

An entity becomes temporal with one call in OnModelCreating:

cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Policy>().IsTemporal();
}

The next dotnet ef migrations add creates its history table. See What a migration creates.

Defaults ​

settingdefault
history table<main table>_history, in the main table's schema
period columnsvalid_from, valid_to
versioned propertiesevery mapped property of the entity, including the properties of its complex and owned members

Customizing ​

Pass a builder to IsTemporal to change any default:

cs
modelBuilder.Entity<Policy>(policy =>
{
    policy.ToTable("policies");

    policy.IsTemporal(temporal => temporal
        .UseHistoryTable("policy_versions", schema: "audit") // default: policies_history, same schema
        .HasPeriodStart("sys_from")                          // default: valid_from
        .HasPeriodEnd("sys_to")                              // default: valid_to
        .Exclude(p => p.UpdatedAt));                         // never copied to history
});
methodeffect
UseHistoryTable(name, schema)Names the history table, and optionally its schema. Changing it later renames the table; see Renaming tables.
HasPeriodStart(column), HasPeriodEnd(column)Names the period columns.
Exclude(property)Leaves a property out of the history; see below.
WithDbSessionUser()Adds the db_session_user audit column; see Change context.
WithRetention()Allows pruning old history with PruneHistoryAsync; see Retention and partitioning.

Excluding properties ​

Exclude removes a property from versioning completely:

  • the history table has no column for it;
  • a SaveChanges that changed only excluded properties writes no history row.

Use it for bookkeeping columns that change often and mean little on their own, such as UpdatedAt or a cached calculation. The sample project excludes Policy.UpdatedAt:

cs
using Hindsight;
using Microsoft.EntityFrameworkCore;

namespace InsuranceSample;

public sealed class InsuranceDbContext(DbContextOptions<InsuranceDbContext> options) : DbContext(options)
{
    public DbSet<Policy> Policies => Set<Policy>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Policy>(e =>
        {
            e.ToTable("policies");
            e.Property(p => p.Status).HasConversion<string>();
            e.IsTemporal(t => t.Exclude(p => p.UpdatedAt));
        });
    }
}

You can exclude some properties of a composite primary key, but not all of them: Hindsight needs at least one key column to tell which history rows belong to which entity.

What can be temporal ​

A temporal entity must be a standalone entity type mapped to its own table, with a primary key. It can have complex properties and owned references stored in that table; their columns are versioned too. Owned collections, JSON-mapped members and inheritance hierarchies are rejected when the model is built. See Model validation for the full list and what to do instead.

Released under the MIT License.