Using Dapper to auto-migrate a postgres database and create the database
- code example : HERE
- create an Extension folder with a HostExtensions static class.
- inside it create a static generic method that takes the host (what configures the startup and lifetime of the app) which returns it after it’s done.
- we add a retry cause when running in a docker-compose, the databse being ready isn’t a gurantee even with
depends-on
specified.
1 2 3 4 5 6
public static class HostExtensions { public static IHost MigrateDatabase<TContext>(this IHost host, int? retry = 0) { } }
- we add a retry cause when running in a docker-compose, the databse being ready isn’t a gurantee even with
- create a
scope
fromhost.Services.CreateScope()
and get the required services1 2 3 4 5 6
using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var configuration = services.GetRequiredService<IConfiguration>(); var logger = services.GetRequiredService<ILogger<TContext>>(); }
- in there add a
try
andcatch
blocks, log depending on your desire and create a connection object using theusing
scope so it releases (closes) the connection when it’s done.- the connection string for
postgres
needs to NOT have the database name, it’ll look like this:"DatabaseSettings": { "ConnectionString": "Server=HOST;Port=PORT;User Id=USER;Password=PASS;", "DatabaseName":"db-name" <- needs to be lower case ! }
1 2 3 4 5 6 7 8
logger.LogInformation("Migrating postresql database."); using var connection = new NpgsqlConnection(configuration.GetValue<string>("DatabaseSettings:ConnectionString")); // open the connection connection.Open(); // get the database name from `appsettings.json` or where'ver you have it string dbName = configuration["DatabaseSettings:DatabaseName"];
- the connection string for
check if the database you’re trying to create exists or not, the name of the database has to be lower case to match the one postgres will create.
1 2 3 4 5 6 7 8
var checkDatabaseExistsSql = $"SELECT 1 FROM pg_database WHERE datname = '{dbName}'"; var databaseExists = connection.ExecuteScalar<bool>(checkDatabaseExistsSql); if (!databaseExists) { var createDatabaseSql = $"CREATE DATABASE {dbName};"; connection.Execute(createDatabaseSql); }
- kill the existsing connection and create a new one that’ll have the
database
you wanna use in it, open it and create acommand
object so you can do the operations you desire:1 2 3 4 5 6 7 8 9 10 11
connection.Close(); // replace the old connection with a new one with the database using var updatedConnection = new NpgsqlConnection($"{configuration["DatabaseSettings:ConnectionString"]}Database={dbName};"); updatedConnection.Open(); using var command = new NpgsqlCommand { Connection = updatedConnection };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
command.CommandText = "DROP TABLE IF EXISTS TABLE-NAME"; command.ExecuteNonQuery(); command.CommandText = @"CREATE TABLE TABLE(Id SERIAL PRIMARY KEY, Name VARCHAR(24) NOT NULL, Description TEXT, ... )"; command.ExecuteNonQuery(); command.CommandText = "INSERT INTO TABLE_NAME(Name, Description) VALUES('STUFF', 'IN HERE');"; command.ExecuteNonQuery(); . . . logger.LogInformation("Migrated postresql database.");
- in the
catch
blockwe except a
NpgsqlException
exception and retry1 2 3 4 5 6 7 8 9 10 11
catch (NpgsqlException ex) { logger.LogError(ex, "An error occurred while migrating the postresql database"); if (retryForAvailability < 50) { retryForAvailability++; System.Threading.Thread.Sleep(2000); MigrateDatabase<TContext>(host, retryForAvailability); } }
- finally we return the
Host
1
return host;
- in
Program
file, we call it providing theProgram class
as the Type1
app.MigrateDatabase<Program>();
test it
spin up a postgres container
1
docker container run -p 5432:5432 -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=admin1234 --name testgres postgres
create a new api project or use an open one and follow thru the steps upove