Entity Framework Code First Validation

Introduction

Most persistence frameworks implement some kind of custom validation of entities before they are sent to the database. By custom I mean something more than just “is not null”, “has XX characters”, etc. This typically includes individual properties as well as validation of the entity as a whole – for example, checking that a property’s value is valid when used together with another property’s value.

Entity Framework Code First is no exception. Out of the box it already offers a number of possibilities for validating entities that cover typical scenarios: validation by attributes or by a validation method. One validation left out is one based on XML, but since Code First doesn’t really use XML, it should be no surprise, and the other is fluent validation, something that really should be supported.

Let’s explore each of these possibilities.

Overriding ValidateEntity

The DbContext class has a virtual method called ShouldValidateEntity that is called for each entity about to be persisted – meaning, inserted or updated –, and, when it returns true – which it does by default – will trigger a call to ValidateEntity, another virtual method. In this method, we have a chance to validate our entities any way we like. An example might be, for instance, checking if the entity to be saved implements IDataErrorInfo and extract validation information from this implementation:

   1: protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<Object, Object> items)
   2: {
   3:     DbEntityValidationResult result = base.ValidateEntity(entityEntry, items);
   4:     IDataErrorInfo dei = entityEntry.Entity as IDataErrorInfo;
   5:     
   6:     foreach (String propertyName in entityEntry.CurrentValues.PropertyNames)
   7:     {
   8:         String errorMessage = dei[propertyName];
   9:  
  10:         if (String.IsNullOrWhiteSpace(errorMessage) == false)
  11:         {
  12:             result.ValidationErrors.Add(new DbValidationError(propertyName, errorMessage));
  13:         }
  14:     }
  15:  
  16:     return (result);
  17: }

For the validation to occur, the ValidateOnSave property must be true, which it is by default.
Don’t forget to always call the base implementation!

Applying Validation Attributes

Another option, which also applies to ASP.NET MVC validation (see http://weblogs.asp.net/ricardoperes/archive/2012/06/03/asp-net-mvc-validation-complete.aspx) is using validation attributes, that is, attributes that inherit from ValidationAttribute. The base ValidateEntity method of DbContext also checks for these attributes, another reason why you should always call it. Let’s see an example:

   1: [Serializable]
   2: [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
   3: public sealed class PositiveAttribute : ValidationAttribute
   4: {
   5:     protected override ValidationResult IsValid(Object value, ValidationContext validationContext)
   6:     {
   7:         Int64 longValue = Convert.ToInt64(value);
   8:  
   9:         if (longValue <= 0)
  10:         {
  11:             return (new ValidationResult("Value cannot be negative or zero"));
  12:         }
  13:  
  14:         return (ValidationResult.Success);
  15:     }
  16: }

You would then apply this to some property in your entity:

   1: public class MyEntity
   2: {
   3:     [Positive]
   4:     public Int64 MyAlwaysPositiveNumber { get; set; }
   5: }

The “problem” with this approach is that you must include any assemblies containing these custom validation attributes together with your model. If they are on the same assembly, there’s no problem.

By the way, you can specify multiple validation attributes and you can even apply them to the whole class, not just a property.

Implementing IValidatableObject

Another option, also common to MVC, is having your entities implement IValidatableObject. This interface defines a contract for returning a collection of validation errors for an entity. Here’s a sample implementation:

   1: public class Contract : IValidatableObject
   2: {
   3:     public String Name { get; set; }
   4:     public DateTime StartDate { get; set; }
   5:     public DateTime EndDate { get; set; }
   6:  
   7:     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
   8:     {
   9:         if (this.StartDate >= this.EndDate)
  10:         {
  11:             yield return (new ValidationResult("The end date is before or the same as the start date", new String[] { "StartDate", "EndDate" }));
  12:         }
  13:     }
  14: }

Handling SavingChanges Event

The “underlying” ObjectContext – which, in fact, is only created if requested – exposes a SavingChanges event which is triggered whenever Entity Framework is about to send changes to the database, typically whenever the SaveChanges method is called. If we handle this event, we can perform our custom validation before our entities are saved, and in case something is wrong we can throw an exception to cancel the saving process:

   1: (ctx as IObjectContextAdapter).ObjectContext.SavingChanges += ObjectContext_SavingChanges;
   2:  
   3: void ObjectContext_SavingChanges(Object sender, EventArgs e)
   4: {
   5:     ObjectContext octx = sender as ObjectContext;
   6:     IEnumerable<Object> entities = octx.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified).Select(x => x.Entity);
   7:  
   8:     //do custom validation and throw an exception if something wrong is found
   9:  
  10: }

This has the arguable advantage that it decouples the validation process from the entities and the context themselves.

Conclusion

In case you are using any of these validation techniques, always surround calls to SaveChanges inside a try…catch block and look out for a DbEntityValidationException or your own exception, if you followed the SavingChanges approach. Inside DbEntityValidationException you have an EntityValidationErrors that contains all the details:

   1: try
   2: {
   3:     ctx.SaveChanges();
   4: }
   5: catch (DbEntityValidationException ex)
   6: {
   7:     foreach (DbEntityValidationResult result in ex.EntityValidationErrors)
   8:     {
   9:         //..
  10:     }
  11: }

Alternatively, you can explicitly call GetValidationErrors and see the collection of errors from all sources, except, of course, SavingChanges, because the context is not actually in the process of saving changes, for all entities currently being tracked by the context.

The order by which these validation processes are applied is:

  1. ValidateEntity
  2. ValidationAttribute (from base ValidateEntity)
  3. IValidatableObject (from base ValidateEntity
  4. SavingChanges (only if no errors are found)

Pick the one that better suits your needs!

                             

No Comments