Let's say I have two independent tables in the same database, tables Book and Cup. I create both with just primary keys (int), called Id. Then, to keep things tidy, I separate them into different projects and create FluentMigrations for both, which will reside in Book.Migrations.dll
and Cup.Migrations.dll
.
Now I realize that maybe my book should be able to have a name, and create a new migration to add a column called name. I set the version for this to be 201408111220 (so the timestamp as of writing this), and call it AddNameToBook. I apply this migration and the database is updated accordingly.
Then I realize that maybe a Cup should have a color. So I create a new migration, in the other project, with a version 201408111221 and call it AddColorToCup. Again I run the migration, and the database is updated.
As far as I know, so far everything should work just fine. What I am unsure about, is if I now add another migration to Book, say 201408111224, apply it, and then try to rollback. Since now the version 201408111221
from the other assembly exists in the VersionInfo -table, how will FluentMigrator handle this? Will it throw an error in my face, or just ignore the row since the current assembly doesn't know anything about it?
Also other comments regarding usage of FluentMigrator this way (with multiple assemblies for one database) are welcome.
I wouldn't recommend this approach. When rolling back or migrating up, the migration versions saved in the VersionInfo table will not match the migration versions in the FluentMigrator assembly. With a bit of bad luck this will crash.
If you want to have two separate projects, then you need two VersionInfo tables. One of the projects can use the default VersionInfo table but the other project will have to create a custom one. See here on how to create custom meta data for the VersionInfo table.
For FluentMigrator this is the same as having two databases. This approach is common when combined with schemas. In your example, you would create a Book schema and a Cup schema in the database and keep them separate from each other.
In my experience, most databases are not big enough to justify this extra overhead but if you have hundreds or thousands of migrations then you should absolutely divide it up into different projects (and divide your database into different schemas).
I wrote a migration loader to help me do this
public class MultiAssemblyMigrationLoader : IMigrationInformationLoader
{
public MultiAssemblyMigrationLoader(IMigrationConventions conventions, IEnumerable<Assembly> assemblies, string @namespace, IEnumerable<string> tagsToMatch)
: this(conventions, assemblies, @namespace, false, tagsToMatch)
{
}
public MultiAssemblyMigrationLoader(IMigrationConventions conventions, IEnumerable<Assembly> assemblies, string @namespace, bool loadNestedNamespaces, IEnumerable<string> tagsToMatch)
{
this.Conventions = conventions;
this.Assemblies = assemblies;
this.Namespace = @namespace;
this.LoadNestedNamespaces = loadNestedNamespaces;
this.TagsToMatch = tagsToMatch ?? new string[0];
}
public IMigrationConventions Conventions { get; private set; }
public IEnumerable<Assembly> Assemblies { get; private set; }
public string Namespace { get; private set; }
public bool LoadNestedNamespaces { get; private set; }
public IEnumerable<string> TagsToMatch { get; private set; }
public SortedList<long, IMigrationInfo> LoadMigrations()
{
var sortedList = new SortedList<long, IMigrationInfo>();
IEnumerable<IMigration> migrations = this.FindMigrations();
if (migrations == null) return sortedList;
foreach (IMigration migration in migrations)
{
IMigrationInfo migrationInfo = this.Conventions.GetMigrationInfo(migration);
if (sortedList.ContainsKey(migrationInfo.Version))
throw new DuplicateMigrationException(string.Format("Duplicate migration version {0}.", migrationInfo.Version));
sortedList.Add(migrationInfo.Version, migrationInfo);
}
return sortedList;
}
private IEnumerable<IMigration> FindMigrations()
{
IEnumerable<Type> types = new Type[] { };
foreach (var assembly in Assemblies)
{
types = types.Concat(assembly.GetExportedTypes());
}
IEnumerable<Type> source = types.Where(t =>
{
if (!Conventions.TypeIsMigration(t))
return false;
if (!Conventions.TypeHasMatchingTags(t, this.TagsToMatch))
return !Conventions.TypeHasTags(t);
return true;
});
if (!string.IsNullOrEmpty(Namespace))
{
Func<Type, bool> predicate = t => t.Namespace == Namespace;
if (LoadNestedNamespaces)
{
string matchNested = Namespace + ".";
predicate = t =>
{
if (t.Namespace != Namespace)
return t.Namespace.StartsWith(matchNested);
return true;
};
}
source = source.Where(predicate);
}
return source.Select(matchedType => (IMigration)matchedType.Assembly.CreateInstance(matchedType.FullName));
}
}
to use this class you just need to hook it up to your MigrationRunner
var runner = new MigrationRunner(mainAssembly, runnerContext, processor);
runner.MigrationLoader = new MultiAssemblyMigrationLoader(runner.Conventions, assemblies, runnerContext.Namespace, runnerContext.NestedNamespaces, runnerContext.Tags);
runner.MigrateUp(true);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With