Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delete whole database and reset the change tracker with EF Core using SQLite

I have a server client architecture where the server provides a Rest API for the clients to synchronize the whole database data. They save it to their local SQLite database. The model is in a shared project and can change sometimes. Therefore the clients need to update their local SQLite database schema. This of course only happens after updating client's software (database file stays unchanged).

It's simply achieved by generally deleting the database file and recreating it afterwards.

_context.Database.EnsureDeleted();
_context.Database.EnsureCreated();

AttachNewDataFromServerToDatabaseContext(_context);

_context.SaveChanges();

The Rest API service is instantiated as singelton and uses always the same database context object. First synchronization works fine. But the next ones fail:

System.InvalidOperationException: The instance of entity type '***' cannot be tracked because another instance with the key value '{id:  ***}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

So the Change Tracker is still aware of the "old" entities although the whole database was deleted.

My thoughts about it:

  • Instantiating a new data context every time the database is synchronized could fix it because the Change Tracker starts at "zero" then. Not a good solution in my eyes.
  • Would be great if EnsureDeleted also "resets" the Change Tracker or you could do it manually.

What do you think about it? Thanks for your help!

like image 289
pschlz Avatar asked Apr 01 '19 12:04

pschlz


1 Answers

  • Instantiating a new data context every time the database is synchronized could fix it because the Change Tracker starts at "zero" then. Not a good solution in my eyes.

I would rather say that this is the "right" solution. The context metadata (a.k.a. Model) by default is cached per context type, db connection is maintained by connection pooling and is opened /closed only when needed anyway. So the only benefit of reusing context instance is to avoid creating a several DbSet instances.

At the same time the tracker would keep a lot of "entity" instances and prevent them of being garbage collection without any need after the SaveChanges call. Not to count potential multithread access issues.

So IMHO that's the way to go - instantiate new context, do something with it and dispose it.

  • Would be great if EnsureDeleted also "resets" the Change Tracker or you could do it manually.

Indeed that would be great. But currently neither EnsureDeleted does that nor EF Core provides a public way to do it manually.

There is an internal way though, with the usual risk that it could be changed in some future EF Core version. Adding

using Microsoft.EntityFrameworkCore.Infrastructure;

would allow you to use something like this

_context.ChangeTracker.GetInfrastructure().ResetState();

probably before _context.Database.EnsureDeleted();. Which basically proves that you should really use the first option (new context).

Update (EF Core 3.0): ChangeTracker no more exposes StateManager even internally, so here we need

using Microsoft.EntityFrameworkCore.Internal;

and respectively

_context.GetDependencies().StateManager.ResetState();
like image 145
Ivan Stoev Avatar answered Nov 14 '22 21:11

Ivan Stoev