Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using multiple FluentMigrator assemblies on same database

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.

like image 296
bobblez Avatar asked Aug 11 '14 09:08

bobblez


2 Answers

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).

like image 70
Daniel Lee Avatar answered Nov 15 '22 12:11

Daniel Lee


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);
like image 27
David Chiew Avatar answered Nov 15 '22 11:11

David Chiew