Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to discard changes to context in EF Core

Tags:

I have a huge list of "flattened" objects in json format, and a somewhat complicated relational DB schema (with about 20 tables corresponding to a flattened object). I'm trying to automate the insertions of those flattened objects in my new relational database:

foreach (var flattenedObject in flattenedObjects) {     _repository.Insert(flattenedObject).Wait();     //some time logging, etc } 

The Insert() method callsAddRangeAsync() and AddAsync() for a number of related objects in different tables.

Since the flattened objects are legacy, I'd say about 0.001% of them are malformed and will violate DB constraints - for example trying to insert a duplicate composite primary key in one of the tables.

I expect these rare errors, thus my idea is - wrap the whole Insert() operation in a transaction - if any piece of the operation is invalid, just don't insert anything and log the error, so I can modify the flattened object manually before trying again. Thus my code looks somewhat similar to this:

public async Task Insert(FlattenedObject fo) {     using (var transaction = _context.Database.BeginTransaction())     {         try         {             //magical code that calls AddAsync for multiple tables         }         catch (Exception ex)         {             transaction.Rollback()             //logging         }     } } 

However, if an error occurs somewhere in my try block (I try to insert an object that violates a composite primary key) my whole context object becomes corrupt.

The object that caused the exception still remains in my DbContext and any following call to AddAsync() in a different transaction triggers a new exception.

I tried recreating my DbContext and repo for every new object in the foreach loop above - but even then if I query:

_context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged); 

I see my old object is still in the new instance of dbContext.

Is there any (elegant) way to tell my context to reset all pending changes - so that I can put it in the catch block whenever an error occurs? I want everything that happens within my failed transaction to stay there and not leak.

like image 933
nikovn Avatar asked Mar 19 '17 09:03

nikovn


People also ask

How do I dispose of DbContext in EF core?

When the controller is being disposed, call dispose on your repository and that should dispose the context. If you are using a service layer and not talking to the repository directly from the controller, then call dispose on the service which will call dispose on repo which will dispose the context.

How do I undo changes in Entity Framework Core?

To rollback changes we can use this behavior of the SaveChanges method. Just change the entity state from Modified to Unchanged, Added to Detached and reload the entity if its state is Deleted. This way is very useful when we need to rollback the changes of a specific entity or specific entities from DbContext.

Should I dispose DbContext?

Don't dispose DbContext objects. Although the DbContext implements IDisposable , you shouldn't manually dispose it, nor should you wrap it in a using statement. DbContext manages its own lifetime; when your data access request is completed, DbContext will automatically close the database connection for you.


2 Answers

The code below worked for me. However, if someone posts a cleaner solution (I still expect there to be something out of the box with EF) I will accept it.

private void ResetContextState() => _context.ChangeTracker.Entries()     .Where(e => e.Entity != null).ToList()     .ForEach(e => e.State = EntityState.Detached); 
like image 149
nikovn Avatar answered Sep 21 '22 00:09

nikovn


Solution :

After years, maybe some of you are still looking, and by a miracle you are using EF Core 5.0. There is a new feature to clear the ChangeTracker :

dbContext.ChangeTracker.Clear(); 

Just call it whenever an update fails.
But do not forget that this is not at all the best practice. Microsoft recommends to create new dbContext for each request, as it is designed to have that short lifespan.
More information here : Microsoft EF Core 5.0: ChangeTracker.Clear Method

Better Solution

But bit more complexe, is to use the DbContextFactory. This can be useful when application code needs to create and dispose context instances manually.

More Information here : Microsoft EF Core 5.0 : DbContextFactory
Enjoy

like image 23
xSodia Avatar answered Sep 23 '22 00:09

xSodia