56

When calling DbContext.SaveChanges, I get a DbUpdateException:

An unhandled exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll. Additional information: An error occurred while updating the entries. See the inner exception for details.

Unfortunately, there is no inner exception (at least, not as far as I can see). Is there any way to see exactly why SaveChanges threw an exception? At the very least, it would be helpful to see what table SaveChanges tried to update with when the error occured.

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
John Reynolds
  • 4,927
  • 4
  • 34
  • 42
  • 1
    Have you tried running the code in a debugging session with the option to stop as soon as an exception is thrown (first chance ex)? – Crono Mar 18 '14 at 21:04
  • Please show a screenshot at least – Pellared Mar 18 '14 at 21:09
  • Open a SQL profiler and run your app until you get the exception again. Look at profiler and last SQL sent from your app to the SQL Server. – pero Mar 18 '14 at 21:12
  • 1
    Also look at DbUpdateException.Entries Property http://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbupdateexception.entries(v=vs.113).aspx – pero Mar 18 '14 at 21:15
  • 4
    @Crono: Tried your suggestion (checked the "Break when this exc type is thrown" box), and now a `$exception` value shows up in the "Locals" pane, with all the details I was looking for. Thx! – John Reynolds Mar 18 '14 at 21:33
  • 1
    @JohnReynolds I added it as an answer. You are welcome. – Crono Mar 18 '14 at 22:02
  • I have the same problem, but not appear any "Locals" when the exception is caught and I choose the action "View Detail..." in Visual Studio – Dherik Sep 08 '14 at 19:44
  • If you use **Entity Framework** you can have a look at my answer on [Solution for “Validation failed for one or more entities. See 'EntityValidationErrors' property for more details](http://stackoverflow.com/questions/21486072/solution-for-validation-failed-for-one-or-more-entities-see-entityvalidatione/29031857#29031857). Hope this helps... – Murat Yıldız Jan 26 '16 at 23:14

11 Answers11

31

This is my override of SaveChanges. It gives me a useful place to put breakpoints:

    public override int SaveChanges()
    {
        try
        {
            return 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);
                }
            }
            throw;
        }
        catch(DbUpdateException e)
        {
           //Add your code to inspect the inner exception and/or
           //e.Entries here.
           //Or just use the debugger.
           //Added this catch (after the comments below) to make it more obvious 
           //how this code might help this specific problem
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            throw;
        }
    }

Reference:

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details

Colin
  • 22,328
  • 17
  • 103
  • 197
  • 4
    If I understand correctly, this not help. The @JohnReynolds get a DbUpdateException, not a DbEntityValidationException – Dherik Sep 08 '14 at 18:35
  • 1
    The "Exception" not return enough information about the error, even "DbUpdateException" provide good information about what happen (table, column), as @JohnReynolds said. – Dherik Sep 09 '14 at 11:19
  • 1
    Worked like a charm! I couldn't delete my entry because my db table had a reference to another table. The catch DbUpdateException e.InnerException pointed me to the faulty SQL. Thank you so much :) – MartinJH Mar 31 '16 at 09:24
20

Here's my override of SaveChanges, showing the additional code to deal with the DbUpdateException (as per the question).

    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException vex)
        {
            var exception = HandleDbEntityValidationException(vex);
            throw exception;
        }
        catch(DbUpdateException dbu)
        {
            var exception = HandleDbUpdateException(dbu);
            throw exception;
        }
    }

    private Exception HandleDbUpdateException(DbUpdateException dbu)
    {
        var builder = new StringBuilder("A DbUpdateException was caught while saving changes. ");

        try
        {
            foreach (var result in dbu.Entries)
            {
                builder.AppendFormat("Type: {0} was part of the problem. ", result.Entity.GetType().Name);
            }
        }
        catch (Exception e)
        {
            builder.Append("Error parsing DbUpdateException: " + e.ToString());
        }

        string message = builder.ToString();
        return new Exception(message, dbu);
    }

I've not made the logging code very specific, but it improves on the standard error message of something like:

The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.

This way, at least I can see which entity has the problem, and that's normally enough to work it out.

a-h
  • 4,244
  • 2
  • 23
  • 29
  • 5
    Good answer but please edit the catch block to use `throw;` instead of `throw exception;`. The later resets the entire stack trace which makes finding the original issue much more difficult in the code if you try to handle it or log it later. – Igor Jan 03 '17 at 18:38
18

Based on Colin's answer, fully detailed information on EF persistence failure can be provided like this:

public bool SaveChangesEx()
{
    try
    {
        SaveChanges();
        return true;
    }
    catch (DbEntityValidationException exc)
    {
        // just to ease debugging
        foreach (var error in exc.EntityValidationErrors)
        {
            foreach (var errorMsg in error.ValidationErrors)
            {
                // logging service based on NLog
                Logger.Log(LogLevel.Error, $"Error trying to save EF changes - {errorMsg.ErrorMessage}");
            }
        }

        throw;
    }
    catch (DbUpdateException e)
    {
        var sb = new StringBuilder();
        sb.AppendLine($"DbUpdateException error details - {e?.InnerException?.InnerException?.Message}");

        foreach (var eve in e.Entries)
        {
            sb.AppendLine($"Entity of type {eve.Entity.GetType().Name} in state {eve.State} could not be updated");
        }

        Logger.Log(LogLevel.Error, e, sb.ToString());

        throw;
    }
}

Beside validation errors, update exception will output both general error and context information.

Note: C# 6.0 is required for this code to work, as it uses null propagation and string interpolation.


For .NET Core the code is slightly changed since possible raised exceptions have a different structure / are populated differently:

    public void SaveChangesEx()
    {
        try
        {
            // this triggers defined validations such as required
            Context.Validate();
            // actual save of changes
            Context.SaveChangesInner();
        }
        catch (ValidationException exc)
        {
            Logger.LogError(exc, $"{nameof(SaveChanges)} validation exception: {exc?.Message}");
            throw;
        }
        catch (DbUpdateException exc)
        {
            Logger.LogError(exc, $"{nameof(SaveChanges)} db update error: {exc?.InnerException?.Message}");
            throw;
        }
        catch (Exception exc)
        {
            // should never reach here. If it does, handle the more specific exception
            Logger.LogError(exc, $"{nameof(SaveChanges)} generic error: {exc.Message}");
            throw;
        }
    }

The Context can be enhanced to automatically reject changes on failure, if the same context is not immediately disposed:

public void RejectChanges()
{
    foreach (var entry in ChangeTracker.Entries().Where(e => e.Entity != null).ToList())
    {
        switch (entry.State)
        {
            case EntityState.Modified:
            case EntityState.Deleted:
                entry.State = EntityState.Modified; //Revert changes made to deleted entity.
                entry.State = EntityState.Unchanged;
                break;
            case EntityState.Added:
                entry.State = EntityState.Detached;
                break;
        }
    }
}

public bool SaveChangesInner()
{
    try
    {
        SaveChanges();
        return true;
    }
    catch (Exception)
    {
        RejectChanges();
        throw;
    }
}
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
  • How do you have e.Entries populated for the DbUpdateException scope ? Mine gives me a null ( difference being that I'm using EF core rather than EF ) – DarkUrse Feb 21 '19 at 14:17
  • @DarkUrse - for .NET core the code is slightly modified. For `DbUpdateException` I simply log `exc?.InnerException?.Message}` (`Entries` is not used anymore. I will add a separate answer to .NET Core because it is quite different from .NET – Alexei - check Codidact Feb 21 '19 at 14:57
  • @DarkUrse - I have added the code I use in own of my .NET Core projects. – Alexei - check Codidact Feb 21 '19 at 15:03
  • That's what I was afraid of, we don't have much information on what this error is. aka. if there is a Foreign Key Constraint exception, I would like to know which FK was violated .. I read somewhere that sqlite is voluntarily evasive on the error message but I was hoping `Entries` would have some magic information in it :) – DarkUrse Feb 22 '19 at 10:00
  • @DarkUrse - I have used .NET Core with SQL Server only and FK violations are thrown back correctly. Do you receive any exception on FK violation? (try with the "catch-all" Exception type first). – Alexei - check Codidact Feb 22 '19 at 10:03
  • You're correct, I meant to indicate that I was using SQLite :S Sorry I was the one evasive in my comment – DarkUrse Feb 22 '19 at 10:04
5

When it seems that the real exception gets lost somewhere, your best bet is to break on every exception. Regardless of if it's catched or swallowed somewhere, in or out your reach, the debugger will break and allow you to see what's going on.

See this MSDN link for more info:

How to: Break When an Exception is Thrown

okapishomapi
  • 175
  • 1
  • 4
  • 13
Crono
  • 10,211
  • 6
  • 43
  • 75
2

I was having the same error "The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.\r\nThe statement has been terminated."

I put the Datetime value field like this Datetime.Now

var person = new Person
            {
               FirstName = "Sebastian",
               LastName = "Back",
               **BirthDate = DateTime.Now,**
               IsActive = true,
            };
sqluser
  • 5,502
  • 7
  • 36
  • 50
1

I faced the same issue
If you are using SQL Server and you have parent and child tables with (FK) FOREIGN KEY,
make sure that the relationship among tables for example CASCADE on Delete or on Update.

0

I got same error and solve like that;

I have a field which data type is datetime and i aware that i am setting it without assigned datetime property. So i change it to "Datetime.Now" then i see problem was solved and it's been saved succesfuly

Taha Karaca
  • 111
  • 1
  • 1
  • 10
0

In my case i was using Stored Procedure to add an entity to DB. My SP is giving the runtime error due to which i was getting this error in C#. I corrected the SP and the EF worked perfectly. So if you are making use of SP to add,edit,delete or update in EF check if the corresponding sp is working correctly.

yogihosting
  • 5,494
  • 8
  • 47
  • 80
0

In my case, i there was created the table as DOMAIN\MyUser.TableName without noticing... Instead, I needed to recreate it as dbo.TableName.

My exception:

{"ClassName":"System.Data.Entity.Infrastructure.DbUpdateException","Message":"An error occurred while updating the entries. See the inner exception for details.","Data":null,"InnerException":{"ClassName":"System.Data.Entity.Core.UpdateException","Message":"An error occurred while updating the entries. See the inner exception for details.","Data":null,"InnerException":{"Errors":[{"Source":".Net SqlClient Data Provider","Number":208,"State":1,"Class":16,"Server":"***","Message":"Invalid object name 'dbo.TableName'.","Procedure":"","LineNumber":1}],"ClientConnectionId":"ab3fcb89-392d-42a5-9548-19a8d0cf0cdb","ClassName":"System.Data.SqlClient.SqlException","Message":"Invalid object name 'dbo.TableName'.","Data":{"HelpLink.ProdName":"Microsoft SQL Server","HelpLink.ProdVer":"15.00.2000","HelpLink.EvtSrc":"MSSQLServer","HelpLink.EvtID":"208","HelpLink.BaseHelpUrl":"http://go.microsoft.com/fwlink","HelpLink.LinkId":"20476"}

Henrique Holtz
  • 326
  • 2
  • 6
0

In my case was a C# datetime field with value '1/1/0001 12:00:00 AM +00:00' when SQL only accepts values after '1/1/1753 12:00:00 AM'. Solution:

return new DateTime(Math.Max(date.Ticks, new DateTime(1900, 1, 1).Ticks));

Carlos Toledo
  • 2,519
  • 23
  • 23
-1

In my case, it was column name miss-match between database table and entity model!