在同一数据库上使用多个FluentMigrator程序集

时间:2014-08-11 09:28:28

标签: c# database-versioning fluent-migrator

假设我在同一个数据库中有两个独立的表,即Book和Cup表。我只使用主键(int)创建了两个,称为Id。然后,为了保持整洁,我将它们分成不同的项目并为两者创建FluentMigrations,它们将驻留在Book.Migrations.dllCup.Migrations.dll中。

现在我意识到也许我的书应该能够有一个名字,并创建一个新的迁移来添加一个名为name的列。我将此版本设置为201408111220(因此写入此时的时间戳),并将其命名为AddNameToBook。我应用此迁移并相应地更新数据库。

然后我意识到也许杯子应该有颜色。所以我在另一个项目中创建了一个新的迁移,版本为201408111221,并将其命名为AddColorToCup。我再次运行迁移,并更新数据库。

据我所知,到目前为止一切都应该正常。我不确定的是,如果我现在添加另一个迁移到Book,比如201408111224,应用它,然后尝试回滚。从现在开始,VersionInfo -table中存在来自其他程序集的版本201408111221,FluentMigrator将如何处理这个问题?它会在我的脸上抛出错误,或者只是忽略该行,因为当前的程序集对它没有任何了解吗?

欢迎以这种方式使用FluentMigrator的其他评论(一个数据库有多个程序集)。

2 个答案:

答案 0 :(得分:3)

我写了一个迁移加载器来帮助我做到这一点

    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));
    }

}

要使用此类,您只需将其连接到MigrationRunner

即可
        var runner = new MigrationRunner(mainAssembly, runnerContext, processor);
        runner.MigrationLoader = new MultiAssemblyMigrationLoader(runner.Conventions, assemblies, runnerContext.Namespace, runnerContext.NestedNamespaces, runnerContext.Tags);
        runner.MigrateUp(true);

答案 1 :(得分:2)

我不推荐这种方法。回滚或向上迁移时,VersionInfo表中保存的迁移版本与FluentMigrator程序集中的迁移版本不匹配。运气不好会崩溃。

如果您想要两个单独的项目,那么您需要两个VersionInfo表。其中一个项目可以使用默认的VersionInfo表,但另一个项目必须创建一个自定义的项目。请参阅here on how to create custom meta data for the VersionInfo table

对于FluentMigrator,这与拥有两个数据库相同。当与模式结合时,这种方法很常见。在您的示例中,您将在数据库中创建Book模式和Cup模式,并使它们彼此分离。

根据我的经验,大多数数据库都不足以证明这个额外的开销,但如果你有数百或数千个迁移,那么你绝对应该将它分成不同的项目(并将数据库划分为不同的模式)。