public PostgreSqlDatabaseProvider( string connectionString, string dbNamePrefix, string?templateDb, bool enableLogging, Func <TDbContext, Task>?seed, Action <NpgsqlDbContextOptionsBuilder>?npgsqlOptions, Action <DbContextOptionsBuilder <TDbContext> >?dbContextOptions) { this.connectionString = connectionString; this.templateDb = templateDb; this.seed = seed; //The max identifier length of postgres is 63 chars. Minus the 22 we're using from the base64-guid the prefix must be max 41 chars. if (dbNamePrefix.Length > 41) { throw new ArgumentException("The max. allowed length of the dbNamePrefix is 41 characters."); } dbName = dbNamePrefix + Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('='); var testDbConnectionString = PostgreSqlUtil.ReplaceDatabaseName(connectionString, dbName); var builder = new DbContextOptionsBuilder <TDbContext>().UseNpgsql(testDbConnectionString, npgsqlOptions); dbContextOptions?.Invoke(builder); if (enableLogging) { builder.UseLoggerFactory(new LoggerFactory(new[] { new XUnitLoggerProvider() })); } builder.AddInterceptors(new CreateDatabaseInterceptor <TDbContext>(CreateDatabase)); options = builder.Options; }
public void DropDatabase(TDbContext dbContext) { if (!dbCreated) { return; } dbCreated = false; pgVersion ??= PostgreSqlUtil.GetServerVersion(connectionString); PostgreSqlUtil.DropDb(connectionString, dbName, pgVersion); }
public void CreateDatabase(TDbContext dbContext) { if (dbCreated) { return; } // Creating a DB from a template can cause an exception when done in parallel. // The lock usually prevents this, however, we still encounter race conditions // where we just have to retry. // 55006: source database "test_template" is being accessed by other users Policy.Handle <NpgsqlException>(e => e.SqlState == "55006") .WaitAndRetry(30, _ => TimeSpan.FromMilliseconds(500)) .Execute(CreateDatabase); void CreateDatabase() { if (dbCreated) { return; } PostgreSqlUtil.CreateDatabase(connectionString, dbName, templateDb); dbCreated = true; //if no template database was given, run the migrations if (templateDb == null) { dbContext.Database.Migrate(); } //For some weird reason any async access to the dbContext causes some kind of task deadlock. The cause for it is the AsyncTestSyncContext from XUnit. //It causes .Wait() to lock indefinitely. It doesn't relate to the connection or the migrate above. //Seems somehow connected to dbContext.SaveChangesAsync() when called in the seed. (at least in my tests). //Running the seed with an extra Task.Run() around works... Task.Run(() => seed?.Invoke(dbContext).Wait()).Wait(); } }