EF Core Entity Validation
Note: I wrote a post about Data Annotations validation here.
Introduction
As you guys may know, Entity Framework "classic" used to do entity validation before saving changes. By default, it used the Data Annotations validator, but you could plug your own mechanism. Problem is, EF Core no longer has that feature, and it's not even on its roadmap. But, as you probably know by now, pretty much anything can be done with EF Core. This time, let's see how we can use interceptors for this.
SaveChangesInterceptor
The SaveChangesInterceptor base class implements the ISaveChangesInterceptor interface, one that is used to tell EF Core to hook to the SavingChanges event. In it, we can inspect all of the entities that are about to be persisted and have our go:
public class DataAnnotationsValidatorSaveChangesInterceptor : SaveChangesInterceptor
{
private void ValidateData(DbContext ctx)
{
foreach (var entity in ctx.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified || x.State == EntityState.Added))
{
Validator.ValidateObject(entity.Entity, new ValidationContext(entity.Entity), true);
}
}
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
ValidateData(eventData.Context!);
return base.SavingChanges(eventData, result);
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
{
ValidateData(eventData.Context!);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
What we're doing here is looking at all entities in the change tracker that are currently marked as Modified or Added, and run them through the Validator class, which implements the Data Annotations validations, but you can certainly use any other validation framework you want. This fires all the Data Annotations validators and throws an exception if any violation of the rules is found. To make it work, we just need to add it to the DbOptionsBuilder, maybe in DbContext.OnConfiguring, or where you configure your context (AddDbContext or similar):
optionsBuilder.AddInterceptors(new DataAnnotationsValidatorSaveChangesInterceptor());
I'll soon publish something about the old and new validation options on the .NET platform. And that's it. Happy validating!