Header menu logo Migrondi

The database service requires a MigrondiConfiguration object, as we use the information from the driver and connection string to stablish a connection to the database.

#r "nuget: Migrondi.Core, 1.0.0-beta-010"

open Migrondi.Core
open Migrondi.Core.Database
open Microsoft.Extensions.Logging

let logger =
  // create a sample logger, you can provide your own
  LoggerFactory.Create(fun builder -> builder.AddConsole() |> ignore)
  |> fun x -> x.CreateLogger("Database")

let config = {
  MigrondiConfig.Default with
      connection =
        "Server=localhost;Database=master;User Id=sa;Password=yourStrong(!)Password;"
      driver = MigrondiDriver.Mssql
}

// create a new instance of the database handler
let db: IMiDatabaseHandler = MiDatabaseHandler(logger, config)

Before working with the database service, you need call the SetupDatabase() call to ensure that the migrations table exists.

Currently, the queries to execute the setup are not exposed, so if you create your own IMiDatabaseHandler implementation, you need to be sure you're creating the correct tracking mechanism for the migrations. In our default case it is just tables in the database but keep it in mind.

db.SetupDatabase()

Once the database is setup, you can start working with migrations.

You will notice that the IMiDatabaseHandler interface returns MigrationRecord objects which may be confusing with Migration objects.

The MigrationRecord object is a representation of the migration that is stored in the database, while the Migration object is a representation of the migration file. While they store similar information, their usage and purpose is different.

// Find what was the last migration applied
let migrations = db.FindLastApplied()

// this method returns an option in case there's no migration applied.

In order to obtain the migrations in the database you can call the ListMigrations() method. It has no parameters and returns a list of MigrationRecord objects.

let existingMigrations = db.ListMigrations()

For cases where you want to check if a particular migration has been applied, you can use the FindMigration(name) method. It returns an option with the MigrationRecord object if it exists.

Keep in mind that the default behavior is to store the name of the migration including the timesamp e.g.

let foundMigration = db.FindMigration("initial-tables_1708216610033")

NOTE: Keep in mind that if you are using a custom implementation of the IMiDatabaseHandler interface, you need to ensure that the MigrationRecord object stores the name properly as well.

Applying and Rolling back migrations is fairly straight forward as well. The general mechanism is to simply iterate over the migration objects, run their SQL content and then store a MigrationRecord object in the database.

A brief example of how we do that internally is as follows:

open System.Data
open RepoDb

let toRun: Migration list = []
let connection: IDbConnection = null // get it from somewhere

for migration in toRun do
  let content = migration.upContent
  // run the content against the database
  connection.ExecuteNonQuery($"{content};;") |> ignore

  connection.Insert(
    tableName = config.tableName,
    entity = {|
      name = migration.name
      timestamp = migration.timestamp
    |},
    fields = Field.From("name", "timestamp")
  )
  |> ignore

The rollback process is similar, but instead of running the upContent we run the downContent and then remove the MigrationRecord from the database rather than inserting.

In your case you would need to ensure that the IMiDatabaseHandler implementation you are using save the MigrationRecord objects properly as well as applying the the migration content to your data source.

Multiple items
namespace Migrondi

--------------------
type Migrondi = interface IMigrondi new: config: MigrondiConfig * database: IMiDatabaseHandler * fileSystem: IMiFileSystem * logger: ILogger -> Migrondi static member MigrondiFactory: config: MigrondiConfig * rootDirectory: string * ?logger: ILogger<IMigrondi> -> IMigrondi

--------------------
new: config: MigrondiConfig * database: IMiDatabaseHandler * fileSystem: FileSystem.IMiFileSystem * logger: ILogger -> Migrondi
namespace Migrondi.Core
Multiple items
namespace Migrondi

--------------------
type Migrondi = interface IMigrondi new: config: MigrondiConfig * database: IMiDatabaseHandler * fileSystem: IMiFileSystem * logger: ILogger -> Migrondi static member MigrondiFactory: config: MigrondiConfig * rootDirectory: string * ?logger: ILogger<IMigrondi> -> IMigrondi

--------------------
new: config: MigrondiConfig * database: Database.IMiDatabaseHandler * fileSystem: FileSystem.IMiFileSystem * logger: Extensions.Logging.ILogger -> Migrondi
namespace Migrondi.Core.Database
namespace Microsoft
namespace Microsoft.Extensions
namespace Microsoft.Extensions.Logging
val logger: ILogger
Multiple items
type LoggerFactory = interface ILoggerFactory interface IDisposable new: unit -> unit + 5 overloads member AddProvider: provider: ILoggerProvider -> unit member CreateLogger: categoryName: string -> ILogger member Dispose: unit -> unit static member Create: configure: Action<ILoggingBuilder> -> ILoggerFactory
<summary> Produces instances of <see cref="T:Microsoft.Extensions.Logging.ILogger" /> classes based on the given providers. </summary>

--------------------
LoggerFactory() : LoggerFactory
LoggerFactory(providers: System.Collections.Generic.IEnumerable<ILoggerProvider>) : LoggerFactory
LoggerFactory(providers: System.Collections.Generic.IEnumerable<ILoggerProvider>, filterOptions: LoggerFilterOptions) : LoggerFactory
LoggerFactory(providers: System.Collections.Generic.IEnumerable<ILoggerProvider>, filterOption: Extensions.Options.IOptionsMonitor<LoggerFilterOptions>) : LoggerFactory
LoggerFactory(providers: System.Collections.Generic.IEnumerable<ILoggerProvider>, filterOption: Extensions.Options.IOptionsMonitor<LoggerFilterOptions>, options: Extensions.Options.IOptions<LoggerFactoryOptions>) : LoggerFactory
LoggerFactory(providers: System.Collections.Generic.IEnumerable<ILoggerProvider>, filterOption: Extensions.Options.IOptionsMonitor<LoggerFilterOptions>, ?options: Extensions.Options.IOptions<LoggerFactoryOptions>, ?scopeProvider: IExternalScopeProvider) : LoggerFactory
LoggerFactory.Create(configure: System.Action<ILoggingBuilder>) : ILoggerFactory
val builder: ILoggingBuilder
(extension) ILoggingBuilder.AddConsole() : ILoggingBuilder
(extension) ILoggingBuilder.AddConsole(configure: System.Action<Console.ConsoleLoggerOptions>) : ILoggingBuilder
val ignore: value: 'T -> unit
val x: ILoggerFactory
(extension) ILoggerFactory.CreateLogger<'T>() : ILogger<'T>
(extension) ILoggerFactory.CreateLogger(``type`` : System.Type) : ILogger
ILoggerFactory.CreateLogger(categoryName: string) : ILogger
val config: MigrondiConfig
type MigrondiConfig = { connection: string migrations: string tableName: string driver: MigrondiDriver } static member Default: MigrondiConfig
<summary> Represents the configuration that will be used to run migrations </summary>
property MigrondiConfig.Default: MigrondiConfig with get
type MigrondiDriver = | Mssql | Sqlite | Postgresql | Mysql static member FromString: value: string -> MigrondiDriver member AsString: string
<summary> DU that represents the currently supported drivers </summary>
union case MigrondiDriver.Mssql: MigrondiDriver
val db: IMiDatabaseHandler
type IMiDatabaseHandler = abstract ApplyMigrations: migrations: Migration seq -> IReadOnlyList<MigrationRecord> abstract ApplyMigrationsAsync: migrations: Migration seq * [<Optional>] ?cancellationToken: CancellationToken -> Task<IReadOnlyList<MigrationRecord>> abstract FindLastApplied: unit -> MigrationRecord option abstract FindLastAppliedAsync: [<Optional>] ?cancellationToken: CancellationToken -> Task<MigrationRecord option> abstract FindMigration: name: string -> MigrationRecord option abstract FindMigrationAsync: name: string * [<Optional>] ?cancellationToken: CancellationToken -> Task<MigrationRecord option> abstract ListMigrations: unit -> IReadOnlyList<MigrationRecord> abstract ListMigrationsAsync: [<Optional>] ?cancellationToken: CancellationToken -> Task<IReadOnlyList<MigrationRecord>> abstract RollbackMigrations: migrations: Migration seq -> IReadOnlyList<MigrationRecord> abstract RollbackMigrationsAsync: migrations: Migration seq * [<Optional>] ?cancellationToken: CancellationToken -> Task<IReadOnlyList<MigrationRecord>> ...
<summary> This service talks directly to the database and is responsible, it provides means to setting up the database before it's first usage, that is important because it will create the migrations table that is used to keep track of the applied migrations. This service also provides both sync and async methods to list, apply and rollback migrations. </summary>
Multiple items
type MiDatabaseHandler = interface IMiDatabaseHandler new: logger: ILogger * config: MigrondiConfig -> MiDatabaseHandler

--------------------
new: logger: ILogger * config: MigrondiConfig -> MiDatabaseHandler
abstract IMiDatabaseHandler.SetupDatabase: unit -> unit
val migrations: MigrationRecord option
abstract IMiDatabaseHandler.FindLastApplied: unit -> MigrationRecord option
val existingMigrations: System.Collections.Generic.IReadOnlyList<MigrationRecord>
abstract IMiDatabaseHandler.ListMigrations: unit -> System.Collections.Generic.IReadOnlyList<MigrationRecord>
val foundMigration: MigrationRecord option
abstract IMiDatabaseHandler.FindMigration: name: string -> MigrationRecord option
namespace System
namespace System.Data
namespace RepoDb
val toRun: Migration list
type Migration = { name: string timestamp: int64 upContent: string downContent: string manualTransaction: bool } static member ExtractFromFilename: filename: string -> Validation<(string * int64),string> static member ExtractFromPath: path: string -> Validation<(string * int64),string>
<summary> Object that represents an SQL migration file on disk, and are often used provide more context or information while logging information about migrations. </summary>
type 'T list = List<'T>
val connection: IDbConnection
type IDbConnection = inherit IDisposable override BeginTransaction: unit -> IDbTransaction + 1 overload override ChangeDatabase: databaseName: string -> unit override Close: unit -> unit override CreateCommand: unit -> IDbCommand override Open: unit -> unit member ConnectionString: string member ConnectionTimeout: int member Database: string member State: ConnectionState
<summary>Represents an open connection to a data source, and is implemented by .NET data providers that access relational databases.</summary>
val migration: Migration
val content: string
Migration.upContent: string
<summary> the actual SQL statements that will be used to run against the database </summary>
(extension) IDbConnection.ExecuteNonQuery(commandText: string, ?param: obj, ?commandType: System.Nullable<CommandType>, ?traceKey: string, ?commandTimeout: System.Nullable<int>, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace) : int
(extension) IDbConnection.Insert<'TEntity (requires reference type)>(entity: 'TEntity, ?fields: System.Collections.Generic.IEnumerable<Field>, ?hints: string, ?commandTimeout: System.Nullable<int>, ?traceKey: string, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace, ?statementBuilder: Interfaces.IStatementBuilder) : obj
(extension) IDbConnection.Insert<'TEntity,'TResult (requires reference type)>(entity: 'TEntity, ?fields: System.Collections.Generic.IEnumerable<Field>, ?hints: string, ?commandTimeout: System.Nullable<int>, ?traceKey: string, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace, ?statementBuilder: Interfaces.IStatementBuilder) : 'TResult
(extension) IDbConnection.Insert<'TEntity (requires reference type)>(tableName: string, entity: 'TEntity, ?fields: System.Collections.Generic.IEnumerable<Field>, ?hints: string, ?commandTimeout: System.Nullable<int>, ?traceKey: string, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace, ?statementBuilder: Interfaces.IStatementBuilder) : obj
(extension) IDbConnection.Insert<'TEntity,'TResult (requires reference type)>(tableName: string, entity: 'TEntity, ?fields: System.Collections.Generic.IEnumerable<Field>, ?hints: string, ?commandTimeout: System.Nullable<int>, ?traceKey: string, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace, ?statementBuilder: Interfaces.IStatementBuilder) : 'TResult
(extension) IDbConnection.Insert(tableName: string, entity: obj, ?fields: System.Collections.Generic.IEnumerable<Field>, ?hints: string, ?commandTimeout: System.Nullable<int>, ?traceKey: string, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace, ?statementBuilder: Interfaces.IStatementBuilder) : obj
(extension) IDbConnection.Insert<'TResult>(tableName: string, entity: obj, ?fields: System.Collections.Generic.IEnumerable<Field>, ?hints: string, ?commandTimeout: System.Nullable<int>, ?traceKey: string, ?transaction: IDbTransaction, ?trace: Interfaces.ITrace, ?statementBuilder: Interfaces.IStatementBuilder) : 'TResult
MigrondiConfig.tableName: string
<summary> The name of the table that will be used to store migration information </summary>
Migration.name: string
Migration.timestamp: int64
Multiple items
type Field = interface IEquatable<Field> new: name: string -> unit + 1 overload member Equals: obj: obj -> bool + 1 overload member GetHashCode: unit -> int member ToString: unit -> string static member (<>) : objA: Field * objB: Field -> bool static member (=) : objA: Field * objB: Field -> bool static member From: name: string -> IEnumerable<Field> + 1 overload static member Parse: obj: obj -> IEnumerable<Field> + 4 overloads member Name: string ...
<summary> An object that is used to signify a field in the query statement. It is also used as a common object in relation to the context of field object. </summary>

--------------------
Field(name: string) : Field
Field(name: string, ``type`` : System.Type) : Field
Field.From([<System.ParamArray>] fields: string array) : System.Collections.Generic.IEnumerable<Field>
Field.From(name: string) : System.Collections.Generic.IEnumerable<Field>

Type something to start searching.