Entity Framework Code First Fluent Validation

Back to Entity Framework Code First (EFCF) validation. On my previous post I mentioned that EFCF did not support fluent validation. While this is true, it isn’t too hard to implement one such mechanism, which is exactly why I am writing this! Smile

I will be using the SavingChanges event to inject the validation logic, which will be implemented by strongly typed delegates. Let’s see some code:

   1: public static class DbContextExtensions
   2: {
   3:     private static IDictionary<Type, Tuple<Delegate, String>> entityValidations = new ConcurrentDictionary<Type, Tuple<Delegate, String>>();
   4:  
   5:     public static void AddEntityValidation<TEntity>(this DbContext context, Func<TEntity, Boolean> validation, String message) where TEntity : class
   6:     {
   7:         if (context == null)
   8:         {
   9:             throw new ArgumentNullException("context");
  10:         }
  11:  
  12:         if (validation == null)
  13:         {
  14:             throw new ArgumentNullException("validation");
  15:         }
  16:  
  17:         if (String.IsNullOrWhiteSpace(message) == true)
  18:         {
  19:             throw new ArgumentNullException("message");
  20:         }
  21:  
  22:         if (entityValidations.ContainsKey(typeof(TEntity)) == false)
  23:         {
  24:             (context as IObjectContextAdapter).ObjectContext.SavingChanges += delegate
  25:             {
  26:                 if (context.Configuration.ValidateOnSaveEnabled == true)
  27:                 {
  28:                     IEnumerable<TEntity> entities = context.ChangeTracker.Entries<TEntity>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).Select(x => x.Entity).ToList();
  29:  
  30:                     foreach (TEntity entity in entities)
  31:                     {
  32:                         String error = ValidateEntity(entity);
  33:  
  34:                         if (String.IsNullOrWhiteSpace(error) == false)
  35:                         {
  36:                             throw (new ValidationException(error));
  37:                         }
  38:                     }
  39:                 }
  40:             };
  41:         }
  42:  
  43:         entityValidations[typeof(TEntity)] = new Tuple<Delegate, String>(validation, message);
  44:     }
  45:  
  46:     private static String ValidateEntity<TEntity>(TEntity entity)
  47:     {
  48:         Type entityType = typeof(TEntity);
  49:  
  50:         if (entityValidations.ContainsKey(entityType) == true)
  51:         {
  52:             Tuple<Delegate, String> entry = entityValidations[entityType];
  53:             Func<TEntity, Boolean> validation = entry.Item1 as Func<TEntity, Boolean>;
  54:  
  55:             if (validation(entity) == false)
  56:             {
  57:                 return (entry.Item2);
  58:             }
  59:         }
  60:  
  61:         return (null);
  62:     }
  63: }

We have an extension method that allows declaring, for an entity type, a validation expression, such as this:

   1: ctx.AddEntityValidation<SomeEntity>(x => x.SomeProperty != null, "SomeProperty is required");

The validation will be fired when the SaveChanges method is called and the errors will be encapsulated in a ValidationException:

   1: try
   2: {
   3:     ctx.SaveChanges();
   4: }
   5: catch (ValidationException ex)
   6: {
   7:     //see content of ex.ValidationResult.ErrorMessage
   8: }

This code can certainly be improved – multiple validations per entity, property-based validations, etc – but I think it is good enough to illustrate my technique.

One final note: the fluent validation will only be fired if the ValidateOnSaveEnabled property is set to true, which is the default.

                             

No Comments