DbEntityValidationException - How can I easily tell what caused the error?
The easiest solution is to override SaveChanges
on your entities class. You can catch the DbEntityValidationException
, unwrap the actual errors and create a new DbEntityValidationException
with the improved message.
- Create a partial class next to your SomethingSomething.Context.cs file.
- Use the code at the bottom of this post.
- That's it. Your implementation will automatically use the overriden SaveChanges without any refactor work.
Your exception message will now look like this:
System.Data.Entity.Validation.DbEntityValidationException: Validation
failed for one or more entities. See 'EntityValidationErrors' property
for more details. The validation errors are: The field PhoneNumber
must be a string or array type with a maximum length of '12'; The
LastName field is required.
You can drop the overridden SaveChanges in any class that inherits from DbContext
:
public partial class SomethingSomethingEntities
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
}
The DbEntityValidationException
also contains the entities that caused the validation errors. So if you require even more information, you can change the above code to output information about these entities.
See also: http://devillers.nl/improving-dbentityvalidationexception/
How to handle System.Data.Entity.Validation.DbEntityValidationException
Add the following code to your DbContext class, then in the validation error message, you will be able to see the details of the validation problem:
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Reference: https://stackoverflow.com/a/15820506/1845408
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details
To be honest I don't know how to check the content of the validation errors. Visual Studio shows me that it's an array with 8 objects, so 8 validation errors.
Actually you should see the errors if you drill into that array in Visual studio during debug. But you can also catch the exception and then write out the errors to some logging store or the console:
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
EntityValidationErrors
is a collection which represents the entities which couldn't be validated successfully, and the inner collection ValidationErrors
per entity is a list of errors on property level.
These validation messages are usually helpful enough to find the source of the problem.
Edit
A few slight improvements:
The value of the offending property can be included in the inner loop like so:
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Value: \"{1}\", Error: \"{2}\"",
ve.PropertyName,
eve.Entry.CurrentValues.GetValue<object>(ve.PropertyName),
ve.ErrorMessage);
}
While debugging Debug.Write
might be preferable over Console.WriteLine
as it works in all kind of applications, not only console applications (thanks to @Bart for his note in the comments below).
For web applications that are in production and that use Elmah for exception logging it turned out to be very useful for me to create a custom exception and overwrite SaveChanges
in order to throw this new exception.
The custom exception type looks like this:
public class FormattedDbEntityValidationException : Exception
{
public FormattedDbEntityValidationException(DbEntityValidationException innerException) :
base(null, innerException)
{
}
public override string Message
{
get
{
var innerException = InnerException as DbEntityValidationException;
if (innerException != null)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine();
foreach (var eve in innerException.EntityValidationErrors)
{
sb.AppendLine(string.Format("- Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().FullName, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
sb.AppendLine(string.Format("-- Property: \"{0}\", Value: \"{1}\", Error: \"{2}\"",
ve.PropertyName,
eve.Entry.CurrentValues.GetValue<object>(ve.PropertyName),
ve.ErrorMessage));
}
}
sb.AppendLine();
return sb.ToString();
}
return base.Message;
}
}
}
And SaveChanges
can be overwritten the following way:
public class MyContext : DbContext
{
// ...
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException e)
{
var newException = new FormattedDbEntityValidationException(e);
throw newException;
}
}
}
A few remarks:
The yellow error screen that Elmah shows in the web interface or in the sent emails (if you have configured that) now displays the validation details directly at the top of the message.
Overwriting the
Message
property in the custom exception instead of overwritingToString()
has the benefit that the standard ASP.NET "Yellow screen of death (YSOD)" displays this message as well. In contrast to Elmah the YSOD apparently doesn't useToString()
, but both display theMessage
property.Wrapping the original
DbEntityValidationException
as inner exception ensures that the original stack trace will still be available and is displayed in Elmah and the YSOD.By setting a breakpoint on the line
throw newException;
you can simply inspect thenewException.Message
property as a text instead of drilling into the validation collections which is a bit awkward and doesn't seem to work easily for everyone (see comments below).
DbEntityValidationException without DRY
In your specific DbContext, just override SaveChanges() and do this:
public class MyDbContext : DbContext
{
...
public override int SaveChanges()
try
{
base.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Debug.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Debug.WriteLine("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
}
}
}
Related Topics
Asp.Net-Mvc: Razor '@' Symbol in Js File
How to Convert JavaScript Datetime to C# Datetime
Refresh Datagridview When Updating Data Source
How to Return JSON with ASP.NET & Jquery
How to Convert JavaScript Date Object to Ticks
How to Load a C# Dll in Python
Python: Inflate and Deflate Implementations
Why C# Implements Methods as Non-Virtual by Default
How to Find a Java to C# Converter
How to Use Java-Style Throws Keyword in C#
Datacontract Xml Serialization and Xml Attributes
Docking Window Inside Another Window
Disable JavaScript Error in Webbrowser Control
How to Override a Non-Virtual Method
How to Find All Possible Subsets of a Given Array
An Attribute Argument Must Be a Constant Expression, ...- Create an Attribute of Type Array