Comment by Ken McCormack on recover-from-dbupdate-exception #1686
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
avatar:
Thanks for the post Phil! I held back a PR on DbContext pooling - because of this concern. I couldn't find any doc on whether EF Core auto-cleans. The symptom first revealed itself in a set of integration tests, which used the same DbContext for a series of fixture creates... (a null string in a non-nullable property will do it), so yes, if the DbContext gets an "unsavable" entity added to its state, all the tests in the suite go red.
(So, would this leak away valid DbContexts in the pool, and cause random failures, slowly bringing the system down??)
So, another callout is that every consumer should not need to handle this... it's an orthogonal concern. Analogous to this are services setting common fields like "CreatedBy" and "CreatedDate" (DRY fail code smell). To get around this, we override SaveChangesAsync in our DbContext, and pass in a 'caller' object, so fields like CreatedBy / UpdatedBy, CreatedDate, UpdatedDate are set centrally (if the type implements a custom interface "IAuditedEntity") so, every repository was doing this, it's now one piece of code - see below -
public virtual async Task SaveChangesAsync(UserId callerId)
{
var now = _dateTimeService.UtcNow; // so that tests are deterministic
// todo - add Detach in a try-catch here, to clean the pooled context
return await base.SaveChangesAsync();
}
So, puzzle... how to write an integration test to replicate a parallel unit of work performing a breaking insert - it needs to interleave with the main flow's read and insert...
I will try inheriting the DbContext ("TestDbContext"), and adding that into the container, casting it first to the base type. The service should resolve the test context. Its overload for SaveChangesAsync would create a new DbContext (if some test condition is met), and perform an insert, then continue with the service's unit of work... calling base.SaveChangesAsync(). This will throw a DbUpdateException, the DbContext should catch, detach the failing entity, and throw. In the test, calling SaveChangesAsync a second time on the same DbContext instance (adding a new, valid, entity), should work.
(That's a theory!)