ChatGPT解决这个技术问题 Extra ChatGPT

DbEntityValidationException - How can I easily tell what caused the error?

I have a project that uses Entity Framework. While calling SaveChanges on my DbContext, I get the following exception:

System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

This is all fine and dandy, but I don't want to attach a debugger every time this exception occurs. More over, in production environments I cannot easily attach a debugger so I have to go to great lengths to reproduce these errors.

How can I see the details hidden within the DbEntityValidationException?


L
Leniel Maccaferri

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/


The generated Entities class already inherits from DbContext so you don't have to add it again on the partial class. You won't break or change anything by adding it to the partial class. In fact, if you add the inheritance from DbContext, Resharper will suggest you remove it: "Base type 'DbContext' is already specificied in other parts."
Why isn't this the default behavior of SaveChanges?
"Why isn't this the default behavior of SaveChanges?" - That is a really good question. This was an amazing solution, it saved me hours! I had to thrown in using System.Linq;
My Create View errors on base.SaveChanges() which is in the try block. It never jumps into the catch block. I got your code to over ride SaveChanges but it never gets into the Catch Block on error.
You should set the inner exception to preserve the stack trace.
E
Eric Hirst

As Martin indicated, there is more information in the DbEntityValidationResult. I found it useful to get both my POCO class name and property name in each message, and wanted to avoid having to write custom ErrorMessage attributes on all my [Required] tags just for this.

The following tweak to Martin's code took care of these details for me:

// Retrieve the error messages as a list of strings.
List<string> errorMessages = new List<string>();
foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
{
    string entityName = validationResult.Entry.Entity.GetType().Name;
    foreach (DbValidationError error in validationResult.ValidationErrors)
    {
        errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
    }
}

Using SelectMany and Aggregate in github by Daring Coders
L
Luis

To view the EntityValidationErrors collection, add the following Watch expression to the Watch window.

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors

I'm using visual studio 2013


$exception is brilliant! that means in the immediate window I can do $exception.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
G
GONeale

While you are in debug mode within the catch {...} block open up the "QuickWatch" window (ctrl+alt+q) and paste in there:

((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors

This will allow you to drill down into the ValidationErrors tree. It's the easiest way I've found to get instant insight into these errors.

For Visual 2012+ users who care only about the first error and might not have a catch block, you can even do:

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage

C
Chris Halcrow

To quickly find a meaningful error message by inspecting the error during debugging:

Add a quick watch for: ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors

Drill down into EntityValidationErrors like this: (collection item e.g. [0]) > ValidationErrors > (collection item e.g. [0]) > ErrorMessage


C
Calvin

Actually, this is just the validation issue, EF will validate the entity properties first before making any changes to the database. So, EF will check whether the property's value is out of range, like when you designed the table. Table_Column_UserName is varchar(20). But, in EF, you entered a value that longer than 20. Or, in other cases, if the column does not allow to be a Null. So, in the validation process, you have to set a value to the not null column, no matter whether you are going to make the change on it. I personally, like the Leniel Macaferi answer. It can show you the detail of the validation issues


L
Luis Toapanta

I think "The actual validation errors" may contain sensitive information, and this could be the reason why Microsoft chose to put them in another place (properties). The solution marked here is practical, but it should be taken with caution.

I would prefer to create an extension method. More reasons to this:

Keep original stack trace

Follow open/closed principle (ie.: I can use different messages for different kind of logs)

In production environments there could be other places (ie.: other dbcontext) where a DbEntityValidationException could be thrown.


J
Juri

For Azure Functions we use this simple extension to Microsoft.Extensions.Logging.ILogger

public static class LoggerExtensions
{
    public static void Error(this ILogger logger, string message, Exception exception)
    {
        if (exception is DbEntityValidationException dbException)
        {
            message += "\nValidation Errors: ";
            foreach (var error in dbException.EntityValidationErrors.SelectMany(entity => entity.ValidationErrors))
            {
                message += $"\n * Field name: {error.PropertyName}, Error message: {error.ErrorMessage}";
            }
        }

        logger.LogError(default(EventId), exception, message);
    }
}

and example usage:

try
{
    do something with request and EF
}
catch (Exception e)
{
    log.Error($"Failed to create customer due to an exception: {e.Message}", e);
    return await StringResponseUtil.CreateResponse(HttpStatusCode.InternalServerError, e.Message);
}

A
Atta H.

Use try block in your code like

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;
}

You can check the details here as well

http://mattrandle.me/viewing-entityvalidationerrors-in-visual-studio/ Validation failed for one or more entities. See 'EntityValidationErrors' property for more details http://blogs.infosupport.com/improving-dbentityvalidationexception/


You third link is a copy of the accepted answer's blog, but on a different site. The second link is a stack overflow question that already references your first link.
So, trying to help someone with proper reference is any issue here?
Yes, your answer shouldn't contain just links. Make a summary that answers the question and then post the link at the end for any further reading.