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:
What do you think about it? Thanks for your help!
- 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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With