This is a very dirty hack, depends a lot on your requirements, and obviously it should never use in production. It has some limitations: for example all the inserts must be made using E.F... but maybe it useful for someone.
In my case: I have a lot of integration tests, I can run it using an in memory Sqlite database and sometimes using the real database. To separate the production and test code I have all the code in a new DbContext that inherits from my real DbContext, in the test I inject this new DbContext.
The code: override the SaveChanges methos and call you own code.
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ProcessSqliteIdentities(ChangeTracker.Entries());
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ProcessSqliteIdentities(ChangeTracker.Entries());
return base.SaveChangesAsync(cancellationToken);
}
The ProcessSqliteIdentities function seeds the identity columns (and saves the last identity for each entity)
//Dictionary to save the last identity for each entity type
private readonly ConcurrentDictionary<Type, int> SqliteIdentities = new();
private void ProcessSqliteIdentities(IEnumerable<EntityEntry> entries)
{
//Just in case
if (Database.ProviderName != "Microsoft.EntityFrameworkCore.Sqlite")
return;
entries
.Where(e => e.State == EntityState.Added)
.Select(e => new
{
e.Entity,
NewIdentity = SqliteIdentities.AddOrUpdate(
e.Entity.GetType(),
1,
(_, currentIdentity) => currentIdentity + 1)
})
.Select(e => e.Entity switch
{
Customer c => c.CustomerId = e.NewIdentity,
Book b => b.BookId = e.NewIdentity,
// Add one line for each of you database types
_ => (object)null! //Just in case you have one entity with no multiple primary keys
})
.ToList(); //Or whatever method you like to force the enumeration of the select
}
I hope it helps someone :)