Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework validation with partial updates

I'm using Entity Framework 5.0 with DbContext and POCO entities. There's a simple entity containing 3 properties:

public class Record
{
    public int Id { get; set; }
    public string Title { get; set; }
    public bool IsActive { get; set; }
}

The Title field is always unmodified, and the UI simply displays it without providing any input box to modify it. That's why the Title field is set to null when the form is sent to the server.

Here's how I tell EF to perform partial update of the entity (IsActive field only):

public class EFRepository<TEntity>
{
   ...
   public void PartialUpdate(TEntity entity, params Expression<Func<TEntity, object>>[] propsToUpdate)
   {
       dbSet.Attach(entity);
       var entry = _dbContext.Entry(entity);
       foreach(var prop in propsToUpdate)
           contextEntry.Property(prop).IsModified = true;
   }
}

and the call:

repository.PartialUpdate(updatedRecord, r => r.IsActive);

Calling SaveChanges method, I get the DbEntityValidationException, that tells me, Title is required. When I set dbContext.Configuration.ValidateOnSaveEnabled = false, everything is OK. Is there any way to avoid disabling validation on the whole context and to tell EF not to validate properties that are not being updated? Thanks in advance.

like image 442
Skog Avatar asked Oct 13 '12 09:10

Skog


3 Answers

If you use partial updates or stub entities (both approaches are pretty valid!) you cannot use global EF validation because it doesn't respect your partial changes - it always validates whole entity. With default validation logic you must turn it off by calling mentioned:

dbContext.Configuration.ValidateOnSaveEnabled = false

And validate every updated property separately. This should hopefully do the magic but I didn't try it because I don't use EF validation at all:

foreach(var prop in propsToUpdate) {
    var errors = contextEntry.Property(prop).GetValidationErrors();
    if (erros.Count == 0) {
        contextEntry.Property(prop).IsModified = true;
    } else {
        ...
    }
}

If you want to go step further you can try overriding ValidateEntity in your context and reimplement validation in the way that it validates whole entity or only selected properties based on state of the entity and IsModified state of properties - that will allow you using EF validation with partial updates and stub entities.

Validation in EF is IMHO wrong concept - it introduces additional logic into data access layer where the logic doesn't belong to. It is mostly based on the idea that you always work with whole entity or even with whole entity graph if you place required validation rules on navigation properties. Once you violate this approach you will always find that single fixed set of validation rules hardcoded to your entities is not sufficient.

One of things I have in my very long backlog is to investigate how validation affects speed of SaveChanges operation - I used to have my own validation API in EF4 (prior to EF4.1) based on DataAnnotations and their Validator class and I stopped using it quite soon due to very poor performance.

Workaround with using native SQL has same effect as using stub entities or partial updates with turned off validation = your entities are still not validated but in addition your changes are not part of same unit of work.

like image 59
Ladislav Mrnka Avatar answered Nov 02 '22 21:11

Ladislav Mrnka


In reference to Ladislav's answer, I've added this to the DbContext class, and it now removes all the properties that aren't modified.
I know its not completely skipping the validation for those properties but rather just omitting it, but EF validates per entity not property, and rewriting the entire validation process anew was too much of hassle for me.

protected override DbEntityValidationResult ValidateEntity(
  DbEntityEntry entityEntry,
  IDictionary<object, object> items)
{
  var result = base.ValidateEntity(entityEntry, items);
  var falseErrors = result.ValidationErrors
    .Where(error =>
    {
      if (entityEntry.State != EntityState.Modified) return false;
      var member = entityEntry.Member(error.PropertyName);
      var property = member as DbPropertyEntry;
      if (property != null)
        return !property.IsModified;
      else
        return false;//not false err;
    });

  foreach (var error in falseErrors.ToArray())
    result.ValidationErrors.Remove(error);
  return result;
}
like image 34
Shimmy Weitzhandler Avatar answered Nov 02 '22 22:11

Shimmy Weitzhandler


This is a remix of previous @Shimmy response and it's a version that I currently use.

What I've added is the clause (entityEntry.State != EntityState.Modified) return false; in the Where:

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    var result = base.ValidateEntity(entityEntry, items);

    var falseErrors = result
        .ValidationErrors
        .Where(error =>
        {
            if (entityEntry.State != EntityState.Modified) return false;
            var member = entityEntry.Member(error.PropertyName);
            var property = member as DbPropertyEntry;
            if (property != null) return !property.IsModified;
            return false;
        });

    foreach (var error in falseErrors.ToArray())
    {
        result.ValidationErrors.Remove(error);
    }

    return result;
}
like image 36
Michael Denny Avatar answered Nov 02 '22 21:11

Michael Denny