Configure EF to gracefully handle deadlocks

This commit is contained in:
Thomas Rittson 2025-12-03 13:53:44 +10:00
parent f5393fa643
commit 1c03ef110e
No known key found for this signature in database
GPG Key ID: CDDDA03861C35E27
2 changed files with 78 additions and 60 deletions

View File

@ -47,22 +47,34 @@ public static class EntityFrameworkServiceCollectionExtensions
{
if (provider == SupportedDatabaseProviders.Postgres)
{
options.UseNpgsql(connectionString, b => b.MigrationsAssembly("PostgresMigrations"));
options.UseNpgsql(connectionString, b =>
{
b.MigrationsAssembly("PostgresMigrations");
b.EnableRetryOnFailure();
});
// Handle NpgSql Legacy Support for `timestamp without timezone` issue
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}
else if (provider == SupportedDatabaseProviders.MySql)
{
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString),
b => b.MigrationsAssembly("MySqlMigrations"));
b =>
{
b.MigrationsAssembly("MySqlMigrations");
b.EnableRetryOnFailure();
});
}
else if (provider == SupportedDatabaseProviders.Sqlite)
{
// SQLite doesn't support EnableRetryOnFailure
options.UseSqlite(connectionString, b => b.MigrationsAssembly("SqliteMigrations"));
}
else if (provider == SupportedDatabaseProviders.SqlServer)
{
options.UseSqlServer(connectionString);
options.UseSqlServer(connectionString, b =>
{
b.EnableRetryOnFailure();
});
}
});
}

View File

@ -826,6 +826,11 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
// Use EF's execution strategy to handle transient failures (including deadlocks)
var strategy = dbContext.Database.CreateExecutionStrategy();
return await strategy.ExecuteAsync(async () =>
{
// Use SERIALIZABLE isolation level to prevent race conditions during concurrent calls
using var transaction = await dbContext.Database.BeginTransactionAsync(System.Data.IsolationLevel.Serializable);
@ -890,6 +895,7 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
await transaction.RollbackAsync();
throw;
}
});
}
private async Task<HashSet<Guid>> GetOrgUserIdsWithDefaultCollectionAsync(DatabaseContext dbContext, Guid organizationId)