how would you design this scenario (using Entity Framework 4.1, Code First and repository pattern): the Visual Studio solution contains the following projects
Solution
|-Web Application Project
|-DAL Project
|-Model Project
So in the Model Project there are various classes. Suppose we have in there a class called User with the following definition (stripped down):
public class User{
[Key]
public int UserId { get; set; }
....
//this property has a unique constraint created in a custom DB Initializer class
public string email { get; set; }
....
}
In the DAL Project reside the repository methods (Insert, Update etc.) and also the Initializer class:
public class MyDatabaseInitializer : IDatabaseInitializer<MyDatabase>
{
public void InitializeDatabase(MyDatabase context)
{
try
{
if (!context.Database.Exists())
{
context.Database.Create();
context.Database.ExecuteSqlCommand(
"ALTER TABLE Users ADD CONSTRAINT uc_Email UNIQUE(Email)");
}
}
catch (Exception ex)
{
throw ex.InnerException;
}
}
}
The Commit method of my Unit of Work class looks like this:
public string Commit()
{
string errorMessage = string.Empty;
try
{
Database.Commit();
}
catch (DbUpdateException updExc)
{
errorMessage = updExc.InnerException.Message;
}
return errorMessage;
}
As you see I'm handling DbUpdateException
in the Commit()
method of the Unit of Work class; this means for each class which could cause an update error, this would be handled here.
Suppose one inserts the User records with the following Data:
(UserId,....,Email,...)
1, ... , person1@mail.com , ...
2, ... , person1@mail.com , ...
It 's obvious that this will cause a DbUpdateException to occur. Of course this can be caught and propagated to the place where it should show up. I have the feeling that this design is completely wrong:
Validation should occur for each property separately: shouldn't this be true also for the uniqueness of values of field? Does this mean that I have to merge DAL and MODEL into one project?
How would I handle errors caused by a violation of the uniqueness for fieldA in table A, fieldB in table B, fieldC in table C? Using a generic error message "The value already exists" or "Uniqueness violation" is not very descriptive!
Should I insert another project-Business layer which takes care of such error handling?
Should I handle the errors in the (ASP.NET MVC) Action/Controller which does the update?
How to handle a proper error message in a multi language application?
I am facing the same situation and at the moment I am handling the exception in my controller.
Consider the following entity:
public class Part
{
public int Id { get; set; }
public string Number { get; set; }
}
I have a unique constraint set up on the 'Number' field in the database so if a duplicate value is entered an exception will be thrown. This is how I am handling the exception:
[HttpPost]
public ActionResult Create(Part part)
{
if (ModelState.IsValid)
{
try
{
db.Parts.Add(part);
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateException e)
{
SqlException s = e.InnerException.InnerException as SqlException;
if (s != null && s.Number == 2627)
{
ModelState.AddModelError(string.Empty,
string.Format("Part number '{0}' already exists.", part.Number));
}
else
{
ModelState.AddModelError(string.Empty,
"An error occured - please contact your system administrator.");
}
}
}
return View(part);
}
All this does is return to the same view and display a validation error to the user like this:
I'm not sure how 'proper' this is but I can't currently think of a better way to handle this (E.G. even if I caught this in my DbContext
derived class and threw a more specific exception I would still need to handle it in the controller in order to do anything with it at runtime).
I'm also not sure if I need to check the inner exception. I modified the code from this post which basically checks for a SqlException
in the inner exceptions and checks the error number (in this case 2627 which is a unique key constraint) before reporting it to the user. If the SQL error number is something else a generic error message is displayed instead.
Update:
I now handle exceptions in a domain service class which is a derivative of the example shown here which allows me to handle the exception outside of the controller.