我正在使用与 TeamCity、NUnit 和 Git 的持续集成。我最近从 FluentMigrator 迁移(请原谅双关语)到实体框架迁移。我这样做主要是为了利用其中的脚手架功能。
但是,有可能签入源代码管理的某些更改,而无需首先将更改搭建到迁移中(想象一下在提交和推送提交之前应用程序未运行的场景)。我正在使用预先测试的提交工作流程,所以我想在预测试中检测这个问题,而不是等到调用 migrate.exe (这在我的工作流程中为时已晚并破坏“绿色存储库”) .
我的问题是,如何创建单元/集成测试来检测迁移模型何时与上下文模型不匹配,以便我可以在尝试迁移之前使构建失败?请注意,我希望它与数据库不匹配,并且不希望从测试中访问数据库。
我尝试过这种方法:
[Test]
public void CheckWhetherEntityFrameworkMigrationsContextIsUpToDate()
{
Assert.DoesNotThrow(() =>
{
CallDatabase();
});
}
private void CallDatabase()
{
using (var ctx = new MyContext("SERVER=(local);DATABASE=MyDatabase;Integrated Security=True;"))
{
var tenant = (from t in ctx.Tenant
select t).FirstOrDefault();
}
}
但是,当存在待处理的迁移时,这总是会失败(而不仅仅是在迁移模型与上下文模型不同步的情况下,这就是我所追求的)。
我已在 EntityFramework 项目上针对此问题添加了一个 work item。希望他们会考虑添加一种方法来做到这一点。
如果有人发现这有用,我通过使用以下代码针对测试数据库运行所有迁移来测试迁移:
[TestClass]
public class MigrationsTests
{
[TestMethod]
public void RunAll()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
// back to 0
migrator.Update("0");
// up to current
migrator.Update();
// back to 0
migrator.Update("0");
}
}
这会测试所有
Up
和 Down
迁移,并在自动迁移关闭时检测待处理的更改。
注意: 确保针对测试数据库运行此操作。在我的例子中,测试项目的
app.config
具有到测试数据库的连接字符串(只是一个本地 SQLExpress 实例)。
这里有一些代码,用于检查所有模型更改是否已搭建到迁移中。
bool HasPendingModelChanges()
{
// NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
var migrationsConfiguration = new Migrations.Configuration();
var migrator = new DbMigrator(migrationsConfiguration);
var scriptingMigrator = new MigratorScriptingDecorator(migrator);
try
{
// NOTE: Using InitialDatabase so history won't be read from the database
scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
}
catch (AutomaticMigrationsDisabledException)
{
return true;
}
return false;
}
我想要迄今为止这里给出的所有答案中最好的,所以这就是我想出的:
[TestClass()]
public class MigrationsTests
{
[TestMethod()]
public void MigrationsUpDownTest()
{
// Unit tests don't have a DataDirectory by default to store DB in
AppDomain.CurrentDomain.SetData("DataDirectory", System.IO.Directory.GetCurrentDirectory());
// Drop and recreate database
BoxContext db = new BoxContext();
db.Database.Delete();
var configuration = new Migrations.Configuration();
var migrator = new DbMigrator(configuration);
// Retrieve migrations
List<string> migrations = new List<string>;
migrations.AddRange(migrator.GetLocalMigrations());
try
{
for (int index = 0; index < migrations.Count; index++)
{
migrator.Update(migrations[index]);
if (index > 0) {
migrator.Update(migrations[index - 1]);
} else {
migrator.Update("0"); //special case to revert initial migration
}
}
migrator.Update(migrations.Last());
}
catch (SqlException ex)
{
Assert.Fail("Should not have any errors when running migrations up and down: " + ex.Errors[0].Message.ToString());
}
// Optional: delete database
db.Database.Delete();
}
[TestMethod()]
public void PendingModelChangesTest()
{
// NOTE: Using MigratorScriptingDecorator so changes won't be made to the database
var migrationsConfiguration = new Migrations.Configuration();
var migrator = new DbMigrator(migrationsConfiguration);
var scriptingMigrator = new MigratorScriptingDecorator(migrator);
try
{
// NOTE: Using InitialDatabase so history won't be read from the database
scriptingMigrator.ScriptUpdate(DbMigrator.InitialDatabase, null);
}
catch (AutomaticMigrationsDisabledException)
{
Assert.Fail("Should be no pending model changes/migrations should cover all model changes.");
}
}
}
有几件事值得注意:
[assembly: InternalsVisibleTo("MyTestsProject")]
添加到 Configuration
类的顶部App.config
中指定一个仅用于测试项目的实体框架连接字符串,因为数据库将被频繁删除和重新创建 - 如果在构建计算机上运行,请记住并行运行可能会导致冲突,因此您可能需要更改每个构建的字符串我认为这比 Pablo Romeo 的代码效果更好。此操作会升级,然后再次降级,以捕获降级脚本中丢失的项目。
[TestFixture]
class MigrationTest
{
[Test]
public void RunAll()
{
var configuration = new Configuration();
var migrator = new DbMigrator(configuration);
// Retrieve migrations
List<string> migrations = new List<string> {"0"}; // Not sure if "0" is more zero than the first item in list of local migrations
migrations.AddRange(migrator.GetLocalMigrations());
migrator.Update(migrations.First());
// Doe een stapje naar voren, en een stapje terug (www.youtube.com/watch?v=2sg1KAxuWKI)
// (Dutch pun) meaning: take a small step forward, and a small step back ;)
for (int index = 0; index < migrations.Count; index++)
{
migrator.Update(migrations[index]);
if (index > 0)
migrator.Update(migrations[index - 1]);
}
migrator.Update(migrations.Last());
migrator.Update(migrations.First());
}
}
EF Core 有一个非常简单的方法:
myDbContext.Database.Migrate();
或者您可以像这样检查待处理的迁移:
var migrator = myDbContext.GetInfrastructure().GetService<IMigrator>();
var migrations = myDbContext.Database.GetPendingMigrations();
foreach (var migration in migrations)
{
migrator.Migrate(migration);
}