Search code examples
c#asp.net-core-webapief-core-2.1model-validation

EF Core not raising ValidationException


I have a base DbContext class like

public abstract class DbContextBase : DbContext
{
    public DbContextBase()
    {
    }

    public DbContextBase(DbContextOptions options)
        : base(options)
    {
    }

    public override int SaveChanges()
    {
        this.ValidateEntities();

        return base.SaveChanges();
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        this.ValidateEntities();

        return base.SaveChangesAsync(cancellationToken);
    }

     public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
     {
        this.ValidateEntities();

        return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

    protected virtual void ValidateEntities()
    {
        var entities = this.ChangeTracker.Entries().
                            Where(s => s.State == EntityState.Added || s.State == EntityState.Modified);

        foreach (var entity in entities)
        {
            var validationContext = new ValidationContext(entity);
            Validator.ValidateObject(entity, validationContext);
        }
    }
}

All my Db Context classes inherit from this base class. The problem is that the line Validator.ValidateObject() is not throwing ValidationException even when there is a validation violation. In the debugger I can see that this line is executed. For example, for below model class I try calling SaveChangesAsync() with Name set to null but the validation is passing:

public class MyModel : IEntity<long>
{
    [Key]
    public long Id { get; set; }

    [Required]
    public string Name { get; set; }
}

IEntity<T> just imposes property Id on all models.


Solution

  • The problem is that the variable entity does not hold entity instance, but change tracker (EntityEntry) instance, so the code is trying to validate the wrong thing.

    So either rename the variables and use entry.Entity property:

    var entries = this.ChangeTracker.Entries()
        .Where(s => s.State == EntityState.Added || s.State == EntityState.Modified);
    
    foreach (var entry in entries)
    {
        var validationContext = new ValidationContext(entry.Entity);
        Validator.ValidateObject(entry.Entity, validationContext);
    }
    

    or keep the code as is, but make sure entities variable holds enumerable of entity instances:

    var entities = this.ChangeTracker.Entries()
        .Where(s => s.State == EntityState.Added || s.State == EntityState.Modified)
        .Select(s => s.Entity); // <--