以编程方式创建代码优先迁移

本文关键字:迁移 代码 创建 编程 方式 | 更新日期: 2023-09-27 18:25:07

我在一个项目中,我们正在为数据库使用实体框架上的代码优先。

我们想改变我们所有的持续集成,以消耗下游生成的MSI包,但EF会带来一些复杂性。

  • 当模型发生变化时,我们必须生成一个基于代码的迁移,否则包将损坏(数据库与模型)
  • 我们倾向于从团队中删除迁移的创建(基于https://msdn.microsoft.com/en-us/data/dn481501.aspx)

我尝试过网络上的各种方法,但大多数似乎都需要将AutomaticMigrations设置为trueAutomaticMigrationDataLossAllowed(请参阅:http://romiller.com/2012/02/09/running-scripting-migrations-from-code/)。

我试图通过.NET反射器来复制Add-Migration的功能,但似乎找不到通过Powershell调用命令System.Data.Entity.Migrations.AddMigrationCommand的方法。

有人对我如何在不做一些极其混乱的事情的情况下接近实现这一目标有任何想法吗?这是我想很多人都想做的事情。。。

非常感谢!

以编程方式创建代码优先迁移

首先,没有办法在visualstudio之外运行Nugetpowershell(它使用DTE)。此外,在没有Visual Studio的情况下编写的所有内容都需要手动插入csproj中(但不是一项艰巨的任务)。

只是为了展示它是如何工作的,我给你发了一些代码行。要测试它们,请创建MyDll dll(一个包含上下文和实体的项目测试),然后使用Enable migrations在MyDll上手动启用迁移(仅用于创建Configuration.cs)。

之后,您可以使用这段代码生成源代码

DbConnectionInfo connectionStringInfo = new DbConnectionInfo(
    "Server=.;Database=MigrationTest;User=sa;Password=dacambiare", "System.Data.SqlClient"); // We shoud retrieve this from App.config
ToolingFacade toolingFacade =  new ToolingFacade(
    "MyDll",   // MigrationAssemblyName. In this case dll should be located in "C:''Temp''MigrationTest" dir
    "MyDll",  // ContextAssemblyName. Same as above
    null,
    "C:''Temp''MigrationTest",   // Where the dlls are located
    "C:''Temp''MigrationTest''App.config", // Insert the right directory and change with Web.config if required
    "C:''Temp''App_Data",
    connectionStringInfo)
{
    LogInfoDelegate = s => {Console.WriteLine(s);},
    LogWarningDelegate = s => { Console.WriteLine("WARNING: " + s); },
    LogVerboseDelegate = s => { Console.WriteLine("VERBOSE: " + s); }
};

ScaffoldedMigration scaffoldedMigration = toolingFacade.Scaffold("MyMigName", "C#", "MyAppNameSpace", false);
Console.WriteLine(scaffoldedMigration.DesignerCode);
Console.WriteLine("==================");
Console.WriteLine(scaffoldedMigration.UserCode);
// Don't forget the resource file that is in the scaffoldedMigration

编辑
我忘记了名称空间,不经常使用,所以这里是

using System;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations.Design;

这不是你问题的真正答案,而是分享我的经验:我会非常小心地使用这种方式为你生成的迁移。很多时候,当我(在VS中)创建迁移时,我会对它们进行审查。我检查提议的更改是否是我想要的,EF并没有试图做一些愚蠢的事情(比如通过删除表并创建一个新表来重命名列/表)。

此外,有时EF缺少一些关键的更改——最近,当我更改nvarchar字段的长度时,我在生成正确的迁移时遇到了问题。这还需要仔细审查迁移情况。

您的迁移也是代码。在迁移投入生产之前,我们正在对其进行同行评审。自动创建迁移会降低这些更改的可见性,并可能导致生产中的数据丢失。

如果您在团队环境中遇到迁移问题,我们已经通过沟通解决了这个问题:每次开发人员签入新的迁移时,都会向该项目的所有其他开发人员发送电子邮件。修复了我们所有的问题。

更新刚刚与一位同事进行了讨论,出现了另一个问题——您将如何进行本地开发?你会在开发人员的机器上创建迁移,确保一切正常吗?然后删除迁移并签入代码?然后您的CI将重新生成迁移?我认为这会让dev在与EF合作时大吃一惊。

您可以使用System.Data.Entity.Migrations.Design.MigrationScaffolder类以编程方式生成迁移,如下所示:

[TestMethod]
public void GenerateTestMigration()
{
    var config = new MyDbMigrationsConfiguration();
    var scaffolder = new MigrationScaffolder(config);
    var pendingMigration = scaffolder.Scaffold("TestMigration");
    Trace.WriteLine(pendingMigration.UserCode);
}

如果存在挂起的迁移或需要创建迁移,我会在构建服务器上使用此技术和其他技术来使构建失败:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Design;
using System.Diagnostics;
using System.Linq;
namespace YabbaDabbaDoo
{
    [TestClass]
    public class MigrationTests
    {
        [TestMethod]
        [TestCategory("RunOnBuild")]
        public void VerifyThereAreNoPendingMigrations()
        {
            // Arrange
            var config = new MyDbMigrationsConfiguration();
            var dbMigrator = new DbMigrator(config);
            // Act
            var pendingMigrations = dbMigrator.GetPendingMigrations().ToList();
            // Visual Assertion
            Trace.WriteLine(pendingMigrations);
            // Assert
            Assert.AreEqual(0, pendingMigrations.Count(), "There are pending EF migrations that need to be ran.");
        }
        [TestMethod]
        [TestCategory("RunOnBuild")]
        public void VerifyDatabaseIsCompatibleWithModel()
        {
            // Arrange
            var context = new MyDbContext();
            // Act
            var isCompatible = context.Database.CompatibleWithModel(false);
            // Visual Assertion
            if (!isCompatible)
            {
                var config = new MyDbMigrationsConfiguration();
                var scaffolder = new MigrationScaffolder(config);
                var pendingMigration = scaffolder.Scaffold("MissingMigration");
                Trace.WriteLine("Missing Migration:");
                Trace.WriteLine("");
                Trace.WriteLine(pendingMigration.UserCode);
            }
            // Assert
            Assert.IsTrue(isCompatible, "The EF model is not compatible with the database. An EF migration needs to be created. See output for sample of missing migration.");
        }
    }
}