Tracing

Hooks before and after every execution for logging, auditing, or cancelling a command outright.

What it is

An ITrace implementation gets BeforeExecution() called right before a command runs and AfterExecution() right after, each keyed by operation name by default so a logger can tell a traced Insert apart from a traced Query.

Writing a trace class

public class NorthwindTrace : ITrace
{
    public void BeforeExecution(CancellableTraceLog log) { }

    public void AfterExecution<TResult>(ResultTraceLog<TResult> log) =>
        logger.Info($"{log.Statement} took {log.ExecutionTime.TotalMilliseconds}ms");
}

connection.Insert<Customer>(customer, trace: new NorthwindTrace());

Injecting it into a repository

Pass the trace instance into BaseRepository or DbRepository’s constructor instead of the connection call, and every operation made through that repository is traced automatically — no trace: argument at each call site.

public class CustomerRepository : BaseRepository<Customer, SqlConnection>
{
    public CustomerRepository(IOptions<AppSettings> settings)
        : base(settings.Value.ConnectionString, new NorthwindTrace()) { }
}

For dependency injection, define an interface that extends ITrace, register the implementation as a singleton, and accept it as a constructor parameter so the container supplies it.

public interface INorthwindTrace : ITrace { }

public class NorthwindTrace : INorthwindTrace { ... }

services.AddSingleton<INorthwindTrace, NorthwindTrace>();

public class NorthwindRepository : DbRepository<SqlConnection>
{
    public NorthwindRepository(IOptions<AppSettings> settings, INorthwindTrace trace)
        : base(settings.Value.ConnectionString, trace) { }
}

Cancelling an execution

Because BeforeExecution runs ahead of the actual database call, calling log.Cancel(true) inside it stops the operation before it ever reaches the server — useful for guarding against a statement that doesn’t match what was expected.