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! ![]()
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:publicstaticclass DbContextExtensions
2: {3:privatestatic IDictionary<Type, Tuple<Delegate, String>> entityValidations = new ConcurrentDictionary<Type, Tuple<Delegate, String>>();
4: 5:publicstaticvoid AddEntityValidation<TEntity>(this DbContext context, Func<TEntity, Boolean> validation, String message) where TEntity : class
6: {7:if (context == null)
8: {9:thrownew ArgumentNullException("context");
10: } 11: 12:if (validation == null)
13: {14:thrownew ArgumentNullException("validation");
15: } 16: 17:if (String.IsNullOrWhiteSpace(message) == true)
18: {19:thrownew 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:privatestatic 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.