Blog

EF Core as Nervous System: Automatic Auditing + Domain Events = Smarter Applications

DbContext
EF
C#
.NET

Imagine your application automatically reacts to every change, logs all its critical activity, and communicates internally without repetitive code. In this post, I reveal how to achieve this using EF Core as the central nervous system of your domain, with two foolproof techniques: automatic auditing and domain events. Includes the exact code I use in real enterprise systems.

๐Ÿง  Auditing and Domain Event Publishing with EF Core

In this post, we will explore how to use Entity Framework Core to implement two fundamental patterns in enterprise applications: entity auditing and domain event publishing.

๐Ÿ” Automatic Entity Auditing

EF Core allows us to implement an automatic auditing system by overriding the SaveChangesAsync method:

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = _currentUserService.UserId;
entry.Entity.Created = _dateTime.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = _currentUserService.UserId;
entry.Entity.LastModified = _dateTime.Now;
break;
}
}
// Rest of the implementation...
}

๐ŸŽฏ Key Benefits

  • ๐Ÿ›  Complete automation of audit fields
  • ๐Ÿ”„ Guaranteed consistency across all changes

๐Ÿ“Œ Centralizing Logic in a Single Place

Requirement: Entities must implement AuditableEntity with properties such as:

  • CreatedBy (who created the record)
  • Created (when it was created)
  • LastModifiedBy (last editor)
  • LastModified (last modification)

๐ŸŒ Domain Events with EF Core

The same context demonstrates an efficient pattern for handling domain events:

var events = ChangeTracker.Entries<IHasDomainEvent>()
.Select(x => x.Entity.DomainEvents)
.SelectMany(x => x)
.Where(domainEvent => !domainEvent.IsPublished)
.ToArray();
var result = await base.SaveChangesAsync(cancellationToken);
await DispatchEvents(events);

โšก Main Features

  1. Safe workflow:
  • โœ… Changes are first committed to the database
  • โœ… Then events are published
  1. Duplicate prevention mechanism:
  • ๐Ÿ” Filters only unpublished events (!domainEvent.IsPublished)
  • ๐Ÿท Marks events as published before sending them
  1. Clean integration:
  • ๐Ÿงฉ Uses an IHasDomainEvent interface to identify entities with events
  • ๐Ÿ“ค Delegates publishing to a specialized service (IDomainEventService)

โš™๏ธ Additional Configuration

The context also includes valuable configurations:

protected override void OnModelCreating(ModelBuilder builder)
{
// Ignore DomainEvent in the model
builder.Ignore<DomainEvent>();
builder.Ignore<List<DomainEvent>>();
// Configure DateTime to use UTC
foreach (var entityType in builder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))
{
property.SetValueConverter(new ValueConverter<DateTime, DateTime>(
v => v.Kind == DateTimeKind.Utc ? v : DateTime.SpecifyKind(v, DateTimeKind.Utc),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc)));
}
}
}
}

๐Ÿ† Conclusion

This approach offers us:

  • ๐Ÿ“Š Robust and automatic auditing
  • ๐Ÿš€ Reliable domain events
  • โฑ Professional UTC date handling
  • ๐Ÿ’Ž Elegant integration into the DbContext