Upgrading from 2.x to 3.0
The upgrade from 2.x to 3.0 should be very smooth, because the API was kept mostly unchanged.
What is new?
FluentMigrator now uses dependency injection and other standard libraries from the ASP.NET Core project extensively. This results in a simpler API, well-defined extension points and in general more flexibility.
New in-process runner initialization
var serviceProvider = new ServiceCollection()
// Logging is the replacement for the old IAnnouncer
.AddLogging(lb => lb.AddFluentMigratorConsole())
// Registration of all FluentMigrator-specific services
.AddFluentMigratorCore()
// Configure the runner
.ConfigureRunner(
builder => builder
// Use SQLite
.AddSQLite()
// The SQLite connection string
.WithGlobalConnectionString("Data Source=test.db")
// Specify the assembly with the migrations
.WithMigrationsIn(typeof(MyMigration).Assembly))
.BuildServiceProvider();
// Put the database update into a scope to ensure
// that all resources will be disposed.
using (var scope = serviceProvider.CreateScope())
{
// Instantiate the runner
var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
// Execute the migrations
runner.MigrateUp();
}
dotnet-fm
as global .NET Core CLI tool
The dotnet-fm
tool is now a global tool and therefore requires the .NET Core 2.1-preview2 tooling. This allows the usage of dotnet fm migrate
from other directories than the project directory.
Connection string handling changes
The library assumes that in ProcessorOptions.ConnectionString is either a connection string or a connection string identifier. This are the steps to load the real connection string.
- Queries all IConnectionStringReader implementations
- When a connection string is returned by one of the readers, then this connection string will be used
- When no connection string is returned, try reading from the next IConnectionStringReader
- When no reader returned a connection string, then return ProcessorOptions.ConnectionString
The connection string stored in ProcessorOptions.ConnectionString might be overridden
by registering the IConnectionStringReader instance PassThroughConnectionStringReader
as scoped service.
When no connection string could be found, the SelectingProcessorAccessor returns a ConnectionlessProcessor instead of the previously selected processor.
Custom migration expression validation
There is a new service IMigrationExpressionValidator with a default implementation DefaultMigrationExpressionValidator that validates the migration expressions before executing them.
This feature allows - for example - forbidding data deletions in a production environment.
Using System.ComponentModel.DataAnnotations
for validation
Breaking Changes
Version 3.0 dropped support for all .NET Framework versions below 4.6.1 and the timeout values are now stored as TimeSpan rather than an int
(of seconds).
Minimum: .NET Framework 4.6.1
Dropping the support for all .NET Framework versions below 4.6.1 was required, because the package now relies on the following libraries:
- Microsoft.Extensions.DependencyInjection
- Microsoft.Extensions.Options
- Microsoft.Extensions.Logging
- Microsoft.Extensions.Configuration
ProcessorOptions.Timeout
is now a TimeSpan
This change is part of the ongoing effort to make the API easier to understand, because it might not be clear if an int timeout
is the timeout in milliseconds, seconds, et cetera. Previously the int
value corresponded to seconds.
ICanBeValidated
not used anymore
The library now uses System.ComponentModel.DataAnnotations
for validation - for example the [Required]
attribute for expression fields that are - one might've guessed it - required.
MigrationGeneratorFactory
not used anymore
The selection of the SQL generator happens using the IGeneratorAccessor service.
MigrationProcessorFactoryProvider
not used anymore
The selection of the migration processor is done with the IProcessorAccessor service.
Obsolete API
Due to the introduction of dependency injection, some important migration runner related parts of the API have been deprecated. This section convers this topic and shows how to switch to the new dependency injection based API.
Migration runner initialization
This section shows the runner initialization both with dependency injection and with the IRunnerContext
.
var serviceProvider = new ServiceCollection()
// Logging is the replacement for the old IAnnouncer
.AddLogging(lb => lb.AddFluentMigratorConsole())
// Registration of all FluentMigrator-specific services
.AddFluentMigratorCore()
// Configure the runner
.ConfigureRunner(
builder => builder
// Use SQLite
.AddSQLite()
// The SQLite connection string
.WithGlobalConnectionString("Data Source=test.db")
// Specify the assembly with the migrations
.WithMigrationsIn(typeof(MyMigration).Assembly))
.BuildServiceProvider();
The runner can now be created and used with:
// Put the database update into a scope to ensure
// that all resources will be disposed.
using (var scope = serviceProvider.CreateScope())
{
// Instantiate the runner
var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
// Execute the migrations
runner.MigrateUp();
}
Differences explained
Dependency Injection
- Allows fluent configuration
- Uses standard libraries
- Dependency Injection
- Options
- Logging
- Uses pluggable APIs
- May use a different DI container under the hood (AutoFac, etc...)
- May use standard logging frameworks (log4net, nlog, etc...)
Obsolete API
- Clunky
- Re-inventing the wheel
IAnnouncer
The IAnnouncer interface (and its implementations) were replaced by ILogger and its implementations.
Logger registration
You can comfortably register the default FluentMigratorConsoleLogger:
var serviceProvider = new ServiceCollection()
.AddLogging(lb => lb.AddFluentMigratorConsole())
.BuildServiceProvider();
Configuring the logger output
Warning
Loggers derived from FluentMigratorLogger may use other logger option classes!
Enabling output of elapsed time
var serviceProvider = new ServiceCollection()
.AddLogging(lb => lb.AddFluentMigratorConsole())
.Configure<FluentMigratorLoggerOptions>(cfg => {
cfg.ShowElapsedTime = true;
})
.BuildServiceProvider();
Enabling output of SQL
Important
Logging the SQL messages might be a security risk. Don't store sensitive data unhashed/unencrypted!
var serviceProvider = new ServiceCollection()
.AddLogging(lb => lb.AddFluentMigratorConsole())
.Configure<FluentMigratorLoggerOptions>(cfg => {
cfg.ShowSql = true;
})
.BuildServiceProvider();
Logger usage
You don't use the loggers directly any more. Instead, you just create a constructor parameter with a type of ILogger or ILogger.
public class MyMigration : ForwardOnlyMigration {
private readonly ILogger<MyMigration> _logger;
public MyMigration(ILogger<MyMigration> logger) {
_logger = logger;
}
public void Up() {
_logger.LogInformation("Creating Up migration expressions");
}
}
Other loggers
There are several other loggers, like:
- LogFileFluentMigratorLoggerProvider for logging SQL statements into a file
- SqlScriptFluentMigratorLoggerProvider for logging SQL statements into a TextWriter
Registration of LogFileFluentMigratorLoggerProvider
var serviceProvider = new ServiceCollection()
.AddSingleton<ILoggerProvider, LogFileFluentMigratorLoggerProvider>()
.Configure<LogFileFluentMigratorLoggerOptions>(opt => {
opt.OutputFileName = "sqlscript.sql";
})
.BuildServiceProvider();
IMigrationRunnerConventions.GetMigrationInfo
This function was replaced by IMigrationRunnerConventions.GetMigrationInfoForMigration, because the instantiation will be done using the dependency injection framework.
IProfileLoader.ApplyProfiles
This function was replaced by IProfileLoader.ApplyProfiles(IMigrationRunner) to avoid circular dependencies.
IProfileLoader.FindProfilesIn(IAssemblyCollection, String))
This function is not used anymore.
IMigrationProcessorOptions
This interface is not used anymore. We use ProcessorOptions instead.
IMigrationProcessorFactory
The factories aren't needed anymore. The registered services provide everything that they need for their configuration.
IRunnerContext and RunnerContext
The properties of this interface/class were refactored into several classes.
Properties moved into RunnerOptions
- ApplicationContext (obsolete!)
- AllowBreakingChange
- NoConnection
- Profile
- StartVersion
- Steps
- Tags
- Task
- TransactionPerSession
- Version
Properties moved into ProcessorOptions
Properties moved into TypeFilterOptions
Properties moved into AppConfigConnectionStringAccessorOptions
Warning
This class only works under the full .NET Framework and is marked as obsolete! Provide access to an IConfiguration service. The FluentMigrator library will use it to call the GetConnectionString extension method.
Properties moved into SelectingProcessorAccessorOptions
Properties moved into AssemblySourceOptions
Properties with no direct replacement
Announcer
: Get your ILogger with dependency injection insteadStopWatch
: Get your IStopWatch with dependency injection instead
WorkingDirectory
This is set directly by the creation of a DefaultConventionSet and adding it as singleton to the service collection.
var conventionSet = new DefaultConventionSet(defaultSchemaName: null, WorkingDirectory);
services.AddSingleton<IConventionSet>(conventionSet)
DefaultSchemaName
This is set directly by the creation of a DefaultConventionSet and adding it as singleton to the service collection.
var conventionSet = new DefaultConventionSet(DefaultSchemaName, workingDirectory: null);
services.AddSingleton<IConventionSet>(conventionSet)
CompatabilityMode
renamed to CompatibilityMode
The spelling has been fixed.
ApplicationContext
It is not needed anymore due to the dependency injection providing all services one may need.
ManifestResourceNameWithAssembly
replaced by ValueTuple
This class was overkill.
MigrationGeneratorFactory
This isn't needed anymore, because all factories must be added dynamically using the ConfigureRunner extension method.
var serviceProvider = new ServiceCollection()
// Registration of all FluentMigrator-specific services
.AddFluentMigratorCore()
// Configure the runner
.ConfigureRunner(
builder => builder
// Add database support
.AddSQLite()
.AddSqlServer2008()
.AddFirebird()
/* TODO: More configuration */
)
/* TODO: Add more services */
.BuildServiceProvider();
Selecting the database
The key is the IProcessorAccessor service and its default implementation SelectingProcessorAccessor, which is configured using the SelectingProcessorAccessorOptions.
Note
When the SelectingProcessorAccessorOptions aren't configured, then the value from the SelectingGeneratorAccessorOptions is used.
Note
When neither a processor nor generator ID was specified, then the added processor will be used - but only where there is only one! When no processor or more than one was specified, then an exception gets thrown.
var serviceProvider = new ServiceCollection()
// Registration of all FluentMigrator-specific services
.AddFluentMigratorCore()
// Configure the runner
.ConfigureRunner(
builder => builder
// Add database support
.AddSQLite()
.AddSqlServer2008()
.AddFirebird()
/* TODO: More configuration */
)
.Configure<SelectingProcessorAccessorOptions>(cfg => {
// Selects SQLite from the set of supported databases
cfg.ProcessorId = "sqlite";
})
/* TODO: Add more services */
.BuildServiceProvider();
MigrationProcessorFactoryProvider
This isn't needed anymore, because all processor factory providers must be added dynamically using the ConfigureRunner extension method.
You can find an example above.
Selecting the database
The key is the IGeneratorAccessor service and its default implementation SelectingGeneratorAccessor, which is configured using the SelectingGeneratorAccessorOptions.
Note
When the SelectingGeneratorAccessorOptions aren't configured, then the value from the SelectingProcessorAccessorOptions is used.
Note
When neither a generator nor processor ID was specified, then the added generator will be used - but only where there is only one! When no generator or more than one was specified, then an exception gets thrown.
var serviceProvider = new ServiceCollection()
// Registration of all FluentMigrator-specific services
.AddFluentMigratorCore()
// Configure the runner
.ConfigureRunner(
builder => builder
// Add database support
.AddSQLite()
.AddSqlServer2008()
.AddFirebird()
/* TODO: More configuration */
)
.Configure<SelectingGeneratorAccessorOptions>(cfg => {
// Selects SQLite from the set of supported databases
cfg.GeneratorId = "sqlite";
})
/* TODO: Add more services */
.BuildServiceProvider();
ITypeMap.GetTypeMap(DbType, int, int)
Sometimes, it is possible that a given database type needs a precision of 0, so we cannot use 0 an indicator for an unspecified value anymore. Therefore, we provide an overload using nullable integer values.
IDbFactory
The implementations will remain, but the interface will be gone.
ICanBeValidated
The library now uses System.ComponentModel.DataAnnotations
for validation - for example the [Required]
attribute for expression fields that are - one might've guessed it - required.
MigrationRunner.MaintenanceLoader
is read-only
Don't set the maintenance loader directly. Just register your own as a service.
FAQ
How do I use my own IConventionSet
(or other service)?
Just register them as your own service.
Service | Scope |
---|---|
IConventionSet |
Singleton |
IAssemblyLoadEngine |
Singleton |
IAssemblySource |
Singleton |
IMaintenanceLoader |
Singleton |
IMigrationSource |
Singleton |