Skip to content

Enabling Hindsight ​

UseHindsight() turns Hindsight on for a context:

cs
services.AddDbContext<AppDbContext>(options => options
    .UseNpgsql(connectionString)
    .UseHindsight());

It does two things:

  • it adds a history table to the EF Core model for every IsTemporal() entity, so migrations create and evolve it;
  • it installs the history writer that fills those tables.

Options ​

cs
services.AddDbContext<AppDbContext>(options => options
    .UseNpgsql(connectionString)
    .UseHindsight(hindsight => hindsight
        .UseHistoryWriter(HistoryWriter.Trigger)
        .WithChangeContext<HttpChangeContextProvider>()));
optiondefaultsee
UseHistoryWriter(HistoryWriter)HistoryWriter.InterceptorChoosing a history writer
WithChangeContext<TProvider>()none: the change-context columns are written NULLChange context

Use the trigger writer in production

The default writer is HistoryWriter.Interceptor for compatibility, but HistoryWriter.Trigger is the recommended one: it also records ExecuteUpdate, ExecuteDelete and raw SQL.

Without dependency injection ​

UseHindsight works on any DbContextOptionsBuilder, including the generic one, which it returns unchanged in type:

cs
var options = new DbContextOptionsBuilder<AppDbContext>()
    .UseNpgsql(connectionString)
    .UseHindsight(hindsight => hindsight.UseHistoryWriter(HistoryWriter.Trigger))
    .Options;

var db = new AppDbContext(options);

PostgreSQL only ​

Hindsight supports the Npgsql provider only. The first time a context is used with Hindsight and any other provider (UseSqlite, UseSqlServer, …), it throws an InvalidOperationException that names the provider it found and the one it needs, rather than failing later in migrations or SQL generation.

Timestamps and TimeProvider ​

The two writers take the time of a change from different places:

  • HistoryWriter.Interceptor takes one timestamp per SaveChanges from the TimeProvider registered in the application's service provider, or TimeProvider.System if there is none. Register a fake one to make history timestamps deterministic in tests:

    cs
    // HistoryWriter.Interceptor stamps history with the TimeProvider registered here,
    // for example a FakeTimeProvider in tests; without one it uses TimeProvider.System.
    services.AddSingleton(clock);
    
    services.AddDbContext<AppDbContext>(options => options
        .UseNpgsql(connectionString)
        .UseHindsight(hindsight => hindsight.UseHistoryWriter(HistoryWriter.Interceptor)));
  • HistoryWriter.Trigger uses PostgreSQL's now() and ignores TimeProvider. In tests, assert on the shape of the periods (contiguous, half-open, one open version) or save in separate transactions.

Released under the MIT License.