I've read several questions and articles about things that can cause a
dbUpdateConcurrencyException and none of them seem to be related to what's happening in my code. I have simple functional tests for my CRUD operations, which are using Entity Framework 6.0.
[TestInitialize] inserts some data, and my
[TestCleanup] deletes that same data. This works fine for all my tests except the test that deletes a record. When the cleanup of that test is called, it throws a
My test cleanup method is just this:
var contracts = this.Context.Contract .Where(c => c.EntryUserID == "FunctionalTests.TestData").ToList(); this.Context.Contract.RemoveRange(contracts); this.Context.SaveChanges(); var customers = this.Context.Customer .Where(c => c.EntryUserID == "FunctionalTests.TestData").ToList(); this.Context.Customer.RemoveRange(customers); this.Context.SaveChanges(); //This throws dbUpdateConcurrencyException??
The test that runs just before this fails is deleted a single record from
Context.Contract. A couple weird things:
Context.Contractworks just fine. It's the cleanup of
Context.Customerthat's breaking, even though
Customer.Contractwas what was deleted during the test.
Contract does have a foreign key to
CustomerId. I'm guessing that this is related; deleting the contracts would change the
Customer entity, since the
Customer entity has a collection of contracts property. But again, I'm loading the customers fresh after I've deleted the contracts; so they are not being modified between loading and deleting.
I have tried deleting items one at a time instead of with
RemoveRange(); same error happens on the first delete. I've tried calling
Reload() on a specific
Customer entity before attempting to delete that
Customer; same result.
Also note this works fine for all the other tests that don't delete a Contract. So deleting all the Contracts before deleting the Customers works just fine normally. It's only when a single Contract has been deleted before deleting the rest of the Contracts in the cleanup.
This is the full error message / stack trace:
System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions. ---> System.Data.Entity.Core.OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.. Result StackTrace:
at System.Data.Entity.Internal.InternalContext.SaveChanges() at System.Data.Entity.Internal.LazyInternalContext.SaveChanges() at System.Data.Entity.DbContext.SaveChanges()
Inner exception has the exact same type and message, with this stack trace:
at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64 rowsAffected, UpdateCommand source) at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update() at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.b__2(UpdateTranslator ut) at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func
2 updateFunction) at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update() at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__35() at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess) at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction) at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.b__27() at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation) at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction) at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options) at System.Data.Entity.Internal.InternalContext.SaveChanges()
I was able to get it to work / work around it by passing the
DbContext that I use in my data setup/teardown to the method that's being tested. In other words, the error seems to be because there was one
DbContext being used for the test setup/teardown, and a different
DbContext being used for the code being tested.
However, this does not really answer any of my questions. I'd rather not have to pass around my
DbContext to make sure it's the same; is there a way I can tell my context to refresh/load from the actual database and not just use what it knows? And also, this doesn't explain at all why deleting the Contracts would work fine, and it only fails on deleting the Customers.
This error is caused because the DBContext used by the test setup and teardown is being loaded when the test setup runs. A different context makes a database change between setup and teardown, and because the first context was loaded during setup, it is based on an "old" version of the database, the one from before the test was run.
The solution is to not have teardown use the same DBContext as the one that setup used. Teardown should load its own new DBContext, so that it will be loaded based on the most recent data.
I had assumed that what matters is the state of the data at the time that the Customers to be deleted were loaded from the database. But apparently what matters is the state of the data at the time that the DBContext was loaded.