I\'m looking for advice on the \"best\" place to put validation logic, such as a duplicate check for an entity, when using Entity Framework Code-First, in an MVC application
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 the DbContext
:
protected override DbEntityValidationResult
ValidateEntity(DbEntityEntry entityEntry,
IDictionary
Embed Context.SaveChanges
in a try catch and create a method to access Context.GetValidationErrors(
). This is in my UnitOfWork
class:
public Dictionary 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 before SaveChanges()
:
[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().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.