Skip to main content

Change History

The Tracked Changes system records additions, deletions, and modifications of EF Core entities in a Lagoon application.

warning

This feature consumes resources and can slow down queries. Enable change tracking only when a client request justifies it.

Recorded information​

The history contains notably:

  • the table name;
  • the identifier of the user who triggered the action;
  • the user name;
  • the date and time of the action;
  • the action type: addition, deletion, or modification;
  • the action detail;
  • the identifier of the modified object;
  • the values before modification in JSON format;
  • the values after modification in JSON format.

The page provided by Lagoon lists these modifications in an exportable grid. The export also contains the JSON columns Before and After.

Configure the DbContext​

Modify the application DbContext to inherit from a context with change tracking.

Base classUsage
LgApplicationTrackedChangeDbContext<TUser>Applications with string user identifier.
LgApplicationTrackedChangeDbContextGuid<TUser>Applications with Guid user identifier.

The constructor must pass IHttpContextAccessor to the base class.

ApplicationDbContext.cs
public class ApplicationDbContext : LgApplicationTrackedChangeDbContextGuid<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor = null)
: base(options, httpContextAccessor)
{
}
}
info

Pierre Fabre applications automatically inherit from the TrackedChangeDbContext context. In this case, proceed directly to migration creation.

Create the tracking table​

Create a migration to add the LgTrackedChanges table.

dotnet ef migrations add AddTrackedChanges

The table is automatically added by the LgApplicationTrackedChangeDbContext<TUser> and LgApplicationTrackedChangeDbContextGuid<TUser> contexts in OnModelCreating.

Enable tracking on an entity​

EnableTrackedChangeAttribute​

Place [EnableTrackedChange] on the entity to track. The AllProperties option indicates that all properties should be tracked by default.

Customer.cs
[EnableTrackedChange(AllProperties = true)]
public class Customer
{
public Guid Id { get; set; }

public string Name { get; set; }

[TrackedChange(Ignored = true)]
public string InternalComment { get; set; }
}

TrackedChangeAttribute​

Place [TrackedChange] on a property when the entity does not use AllProperties = true. The Ignored option allows you to exclude a field when AllProperties is active.

Order.cs
[EnableTrackedChange]
public class Order
{
public Guid Id { get; set; }

[TrackedChange]
public string Status { get; set; }
}

The entity must have [EnableTrackedChange] for its fields to be tracked.

Add action detail​

Tracked change contexts expose an overload of SaveChangesAsync that accepts actionDetails.

OrderService.cs
public async Task SaveSupplierAsync(ApplicationDbContext db, CancellationToken cancellationToken)
{
// Adds business context to the action visible in the history.
await db.SaveChangesAsync("Supplier", cancellationToken);
}

This detail is useful when the same table carries multiple business concepts, for example manufacturers and suppliers in a BusinessPartner table.

Temporarily disable tracking​

Contexts implementing ILgApplicationTrackedChangeDbContext expose TrackedChangeDisabled.

ImportService.cs
public async Task ImportAsync(ApplicationDbContext db, CancellationToken cancellationToken)
{
// Disables change tracking during a technical import.
db.TrackedChangeDisabled = true;
await db.SaveChangesAsync(cancellationToken);
db.TrackedChangeDisabled = false;
}

View policy​

Lagoon provides the Policies.TrackedChangeReader policy, with value trackedchange.read. Associate it with the role authorized to view the history, for example an administrator role.

Policies.cs
private static RequireRolePolicies<Roles, P> GetRequireRolePolicies()
{
return new()
{
// Gives access to modification history to administrators.
{ Roles.Admin, P.TrackedChangeReader }
};
}

Add the page to the menu​

The pre-built page is exposed by Lagoon.UI.GridView.Pages.LgPageTrackedChanges.Link().

MenuConfiguration.razor
<AuthorizeView Policy="@Policies.TrackedChangeReader">
<Authorized>
<LgMenuItem Text="#TrackedChanges" IconName="@IconNames.All.ClockHistory" Link="@Lagoon.UI.GridView.Pages.LgPageTrackedChanges.Link()" />
</Authorized>
</AuthorizeView>

Translate table and field names​

To translate the names displayed by the change tracking interface, add keys to Dico.xml.

Dico.xml
<string key="tc.TableName" fr="Nom de ma table" en="My table name" />
<string key="tc.TableName.FieldName" fr="Nom de mon champ" en="My field name" />

Customize recorded information​

The tracked change context exposes several virtual methods to override.

ApplicationDbContext.cs
protected override string GetTrackedChangeTableName(string entityName, string tableName)
{
// Customizes the table name stored in the history.
return entityName;
}

protected override string GetTrackedChangeFieldName(string entityName, string propertyName, string tableName, string fieldName)
{
// Customizes the field name stored in the history.
return propertyName;
}

protected override string[] GetTrackedChangePrimaryKeys(string entityName, IEntityType metadata)
{
// Uses a specific key for a particular entity.
return entityName == nameof(Foo)
? new string[] { nameof(Foo.FooPK) }
: base.GetTrackedChangePrimaryKeys(entityName, metadata);
}

protected override void OnNewTrackedChange(TrackedChangeGuid trackedChange)
{
base.OnNewTrackedChange(trackedChange);

// Add additional information to the new history here.
}