3

I have the following code (EntityContext.Current is a DbContext):

try {
    Setting s = new Setting
    {
        id = 1,
        user_id = 0
    };

    EntityContext.Current.Settings.Add(s);
    EntityContext.Current.SaveChanges(); //this violates a foreign key constraint and therefore throws an exception
} catch (Exception ex) {
    ErrorContext.Log(ex);
}

What I would expect (as per this answer) is that the pending changes would be rolled back when they have failed. However, In my error logging method, I see that they have not been.

Error e = new Error
{
    message = ex.Message
};

EntityContext.Current.Errors.Add(e);

var pending = ((IObjectContextAdapter)EntityContext.Current).ObjectContext.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
//this contains both my new error entity and my old setting entity which shouldn't be here

EntityContext.Current.SaveChanges();

When my application attempts to log the error in the database, it also tries (again) to save the setting with the invalid foreign key constraint, which then causes the addition of the error log record to fail.

This is the first time I've ran into this behavior with EF. I tried wrapping the first SaveChanges() in a transaction but the same issue occurred.

As per this answer my connection string does not contain Enlist=false either. I'm at a loss. Is this expected behavior? And how can I prevent this from happening?

Community
  • 1
  • 1
Mansfield
  • 14,445
  • 18
  • 76
  • 112

1 Answers1

3

This is expected behaviour. SaveChanges attempts to commit all changes in a single database transaction. If it fails the database transaction is rolled back and nothing will be written to the database.

But the failed transaction won't change the state of entities in the context. That's why your Setting entity is still in state Added.

Attaching your Error entity to the same context is a problem since you often won't be able to store it to the database, like in this example. I would consider to write the error log always in a new context with a dedicated method that just only opens a new context, adds the Error entity to this context and saves the changes. Then dispose it immediately.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • I see...very interesting. I suppose I would also be able to simply remove that entity in the catch block? That way when I go to write the error (using the same context) it would no longer try to write the setting as well? – Mansfield Apr 18 '13 at 01:07
  • 1
    @Mansfield: Yes, you can detach the entity. For this simple example it is easy. But imagine you had a more complex scenario and the context would contain lots of entities in Added, Deleted, Modified states and one of them causes a FK constraint violation. In most cases it will be hard to find out the problematic entity in the catch block and to recover from the error. – Slauma Apr 18 '13 at 11:31