Updating Audit Columns in Aggregate Roots and Child Entities with EF Core and DDD

leelanagaramya

Leela Naga Ramya

Posted on July 29, 2024

Updating Audit Columns in Aggregate Roots and Child Entities with EF Core and DDD

Introduction

In enterprise applications, tracking the creation and modification of data is crucial for auditing and accountability. This is often implemented using audit columns like CreatedOn, CreatedBy, UpdatedOn, and UpdatedBy. In a system that follows Domain-Driven Design (DDD) principles, managing these audit columns across aggregate roots and their child entities can be a bit challenging. This blog post will guide you through a practical approach to updating these audit columns using Entity Framework Core.

Understanding the Domain and Entities

In DDD, an aggregate root is the main entity in a group of associated entities, ensuring the consistency and integrity of the entire aggregate. For our example, we'll assume that all entities implementing the IAuditedEntity interface need to have their audit columns updated.

public interface IAuditedEntity
{
DateTime CreatedOn { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedOn { get; set; }
string UpdatedBy { get; set; }
}

Entities that implement this interface will have their audit properties set whenever changes are made.

The SaveChangesAsync Method Override

To automatically set the audit columns, we can override the SaveChangesAsync method in our DbContext. This method intercepts the save operation and allows us to set audit columns before the changes are committed to the database.

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
SetAuditColumns();
return base.SaveChangesAsync(cancellationToken);
}

Implementing the SetAuditColumns Method

The SetAuditColumns method iterates over all the tracked entities and sets the audit properties based on their state (Added or Modified). This ensures that every time an entity is added or updated, the audit columns are populated accordingly.

private void SetAuditColumns()
{
foreach (var item in ChangeTracker.Entries()
.Where(e => e.Entity is IAuditedEntity))
{
var entity = item.Entity as IAuditedEntity;
switch (item.State)
{
case EntityState.Added:
entity.CreatedOn = DateTime.UtcNow;
entity.UpdatedOn = DateTime.UtcNow;
entity.CreatedBy = string.IsNullOrEmpty(this.requestContext.Email) ? "System" : this.requestContext.Email;
entity.UpdatedBy = string.IsNullOrEmpty(this.requestContext.Email) ? "System" : this.requestContext.Email;
break;
case EntityState.Modified:
entity.UpdatedOn = DateTime.UtcNow;
entity.UpdatedBy = string.IsNullOrEmpty(this.requestContext.Email) ? "System" : this.requestContext.Email;
break;
}
}
}

More about RequestContext:

The requestContext is a service populated at an earlier stage in the application's request pipeline. It typically extracts information about the user who initiated the request, such as their email address, and other relevant data. This context is then injected into the DbContext, allowing it to be consumed wherever needed, including in our audit column logic.

public class RequestContext
{
public string Email { get; set; }
// Other properties like UserId, UserName, etc.
}

Conclusion

Automating the population of audit columns in your entities is a crucial aspect of maintaining data integrity and accountability. By leveraging EF Core's ChangeTracker and DDD principles, you can efficiently manage these properties across your aggregate roots and child entities. This approach not only reduces boilerplate code but also ensures consistency and accuracy in your audit data.

💖 💪 🙅 🚩
leelanagaramya
Leela Naga Ramya

Posted on July 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related