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!

                             

8 Comments

  • It's even => It's not even

  • Thanks, @Erik!

  • This is a pretty neat trick to reintroduce entity validation in EF Core using `SaveChangesInterceptor`. Hooking into the `SavingChanges` event and validating the entities marked as Modified or Added gets the job done. But here’s something I’m curious about—how do you think this approach would hold up with a ton of entities in the change tracker? Any potential performance hits, or maybe a more efficient way to handle validations when things get more complex? Can’t wait to see what you think, and looking forward to that post on the new validation options in .NET!

  • چاپ فاکتور: thanks, for sure it will have some performance penalty, but it is probably neglegtable. Something worth trying and measuring, though

  • This library is pretty neat and adds some attribute support for EF:
    https://github.com/efcore/EFCore.CheckConstraints

  • @Nick: thanks, I just wanted to show how the built-in livrary works, but you’re right, there are libraries out there that add very useful functionality.

  • This is a great way to reintroduce entity validation in EF Core using SaveChangesInterceptor. It’s flexible, letting you use Data Annotations or other frameworks. Question: How does this handle nested or complex objects needing recursive validation? Does Validator.ValidateObject manage them, or would I need custom logic for deeper property validations within complex entity structures?

  • خرید روتاتور: Thanks for your question. As stated in https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validator.tryvalidateobject, "This method evaluates each ValidationAttribute instance that is attached to the object type. It also checks whether each property that is marked with RequiredAttribute is provided. It does not recursively validate the property values of the object.".

Add a Comment

As it will appear on the website

Not displayed

Your website