Blog

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

DbContext
EF
C#
.NET
Azure

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