假设我在同一个数据库中有两个独立的表,表 Book 和 Cup。我仅使用主键 (int) 创建这两个键,称为 Id。然后,为了保持整洁,我将它们分成不同的项目,并为这两个项目创建 FluentMigrations,它将驻留在
Book.Migrations.dll
和 Cup.Migrations.dll
中。
现在我意识到也许我的书应该能够有一个名称,并创建一个新的迁移来添加一个名为 name 的列。我将其版本设置为 201408111220(因此是撰写本文时的时间戳),并将其命名为 AddNameToBook。我应用此迁移并相应更新数据库。
然后我意识到也许杯子应该有颜色。因此,我在另一个项目中创建了一个新的迁移,版本为 201408111221,并将其命名为 AddColorToCup。我再次运行迁移,并且数据库已更新。
据我所知,到目前为止一切都应该工作得很好。我不确定的是,如果我现在向 Book 添加另一个迁移(例如 201408111224),应用它,然后尝试回滚。由于现在来自其他程序集的版本
201408111221
存在于 VersionInfo 表中,FluentMigrator 将如何处理这个问题?它会在我面前抛出错误,还是因为当前程序集对此一无所知而忽略该行?
也欢迎有关以这种方式使用 FluentMigrator(一个数据库使用多个程序集)的其他评论。
我不推荐这种方法。回滚或向上迁移时,VersionInfo 表中保存的迁移版本将与 FluentMigrator 程序集中的迁移版本不匹配。如果运气不好的话,这会崩溃。
如果您想要有两个独立的项目,那么您需要两个 VersionInfo 表。其中一个项目可以使用默认的 VersionInfo 表,但另一个项目必须创建一个自定义版本信息表。请参阅此处了解如何为 VersionInfo 表创建自定义元数据。
对于 FluentMigrator,这与拥有两个数据库相同。这种方法在与模式结合时很常见。在您的示例中,您将在数据库中创建一个 Book 架构和一个 Cup 架构,并将它们彼此分开。
根据我的经验,大多数数据库都不够大,不足以证明这种额外的开销是合理的,但如果您有数百或数千个迁移,那么您绝对应该将其划分为不同的项目(并将数据库划分为不同的模式)。
我编写了一个迁移加载器来帮助我做到这一点
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);