Your problem with ValidateEntity appears to be that the validation occurs on SaveChanges and this is too late for you. But in Entity Framework 5.0 you can call the validation earlier if you wish using DbContext.GetValidationErrors. And of course you could also just call DbContext.ValidateEntity directly. This is how I do it:
-
Override the
ValidateEntity
method on theDbContext
:protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { //base validation for Data Annotations, IValidatableObject var result = base.ValidateEntity(entityEntry, items); //You can choose to bail out before custom validation //if (result.IsValid) // return result; CustomValidate(result); return result; } private void CustomValidate(DbEntityValidationResult result) { ValidateOrganisation(result); ValidateUserProfile(result); } private void ValidateOrganisation(DbEntityValidationResult result) { var organisation = result.Entry.Entity as Organisation; if (organisation == null) return; if (Organisations.Any(o => o.Name == organisation.Name && o.ID != organisation.ID)) result.ValidationErrors .Add(new DbValidationError("Name", "Name already exists")); } private void ValidateUserProfile(DbEntityValidationResult result) { var userProfile = result.Entry.Entity as UserProfile; if (userProfile == null) return; if (UserProfiles.Any(a => a.UserName == userProfile.UserName && a.ID != userProfile.ID)) result.ValidationErrors.Add(new DbValidationError("UserName", "Username already exists")); }
-
Embed
Context.SaveChanges
in a try catch and create a method to accessContext.GetValidationErrors(
). This is in myUnitOfWork
class:public Dictionary<string, string> GetValidationErrors() { return _context.GetValidationErrors() .SelectMany(x => x.ValidationErrors) .ToDictionary(x => x.PropertyName, x => x.ErrorMessage); } public int Save() { try { return _context.SaveChanges(); } catch (DbEntityValidationException e) { //http://blogs.infosupport.com/improving-dbentityvalidationexception/ var errors = e.EntityValidationErrors .SelectMany(x => x.ValidationErrors) .Select(x => x.ErrorMessage); string message = String.Join("; ", errors); throw new DataException(message); } }
-
In my controller, call
GetValidationErrors()
after adding the entity to the context but beforeSaveChanges()
:[HttpPost] public ActionResult Create(Organisation organisation, string returnUrl = null) { _uow.OrganisationRepository.InsertOrUpdate(organisation); foreach (var error in _uow.GetValidationErrors()) ModelState.AddModelError(error.Key, error.Value); if (!ModelState.IsValid) return View(); _uow.Save(); if (string.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index"); return Redirect(returnUrl); }
My base repository class implements InsertOrUpdate
like this:
protected virtual void InsertOrUpdate(T e, int id)
{
if (id == default(int))
{
// New entity
context.Set<T>().Add(e);
}
else
{
// Existing entity
context.Entry(e).State = EntityState.Modified;
}
}
I still recommend adding a unique constraint to the database because that will absolutely guarantee your data integrity and provide an index that can improve the efficiency, but overriding ValidateEntry gives loads of control over how and when validation occurs.