【问题标题】:Adding extended logic to a migration向迁移添加扩展逻辑
【发布时间】:2021-06-22 16:10:00
【问题描述】:

我正在尝试通过迁移来管理复杂的数据模型更改,但我不确定这是不是最好的方法。

在本例中,我有一个 Customer 实体,它具有以下属性:

public string Address { get; set; }
public string PostalOrZip { get; set; }
public string ProvinceOrState { get; set; }

现在,我想创建一个名为 CustomerAddress 的新实体,它拥有相同的 3 个属性,但允许模型为 Customer 保存多个地址

我已经这样做并创建了如下迁移

public partial class customeraddress_table : Migration
{
    
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "CustomerAddress",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                CustomerId = table.Column<int>(type: "int", nullable: false),
                Address = table.Column<string>(type: "nvarchar(max)", nullable: true),
                PostalOrZip = table.Column<string>(type: "nvarchar(max)", nullable: true),
                ProvinceOrState = table.Column<string>(type: "nvarchar(max)", nullable: true),
                CreatedOn = table.Column<DateTime>(type: "datetime2", nullable: false),
                ModifiedOn = table.Column<DateTime>(type: "datetime2", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_CustomerAddress", x => x.Id);
                table.ForeignKey(
                    name: "FK_CustomerAddress_Customer_CustomerId",
                    column: x => x.CustomerId,
                    principalTable: "Customer",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
            });

        migrationBuilder.CreateIndex(
            name: "IX_CustomerAddress_CustomerId",
            table: "CustomerAddress",
            column: "CustomerId");


        
      
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "CustomerAddress");
    }
}

我想在迁移中做的是遍历所有Customers 并为每个CustomerAddress 创建一个CustomerAddress,并从Customer 复制地址信息。

我一直坚持使用 migrationBuilder.Sql,但使用上下文会很棒,因为我在 SaveChangesAsync 中有有用的逻辑,我需要在 SQL 中复制这些逻辑。

迁移可以处理这样的要求吗?那么有没有比迁移更好的方法呢?

【问题讨论】:

  • 您可以做的是在生成的代码之后添加您自己的自定义代码,甚至更好地生成一个新的空白迁移文件,并在 UP 方法中指定migrationBuilder.Sql(@"wrtie SQL here to move data..."),您将在其中进行您喜欢的数据操作

标签: c# .net-core entity-framework-core asp.net-core-5.0


【解决方案1】:

我会创建一个包含您的三个属性的地址类。

然后您可以使用一对多关系在您的 CustomerAddress 类中存储一个列表。然后,您可以将其作为新的迁移提交。

然后您可以编写一个函数,该函数将通过现有的客户对象表来填充您的新表/模型。

该函数将获取每个 Customer 对象,使用属性创建一个 Address 对象,并将该地址添加到新 CustomerAddress 对象的列表中。

之后,您可以根据需要简单地删除客户表

【讨论】:

    【解决方案2】:

    我在迁移结束时添加了以下代码

    //Copy the address info from the Customer object into a new CustomerAddress
    migrationBuilder.Sql(@"INSERT INTO CustomerAddress
        SELECT Id as CustomerId, [Address], PostalOrZip, ProvinceOrState, GETDATE() as 
        CreatedOn, GETDATE() as ModifiedOn
        FROM Customer");
    

    这很好用,但通常情况下,我的上下文 SaveChangesAsync() 中的一些逻辑会填充日期字段,我必须在 SQL 代码中执行此操作。这就是我想使用上下文而不是直接 SQL 的原因。

    【讨论】:

      【解决方案3】:

      tl;博士

      绝对应该使用.Sql 来执行这种类型的数据操作。在迁移期间,DbContext 无法使用,因为数据库模式尚未符合模型预期,即迁移点,以准备数据库,以便DbContext 的当前版本可以正常工作。

      重要的是,迁移逻辑可以并且需要在不依赖于您的模型的情况下执行,如果您考虑在连接字符串中使用全新数据库启动应用程序而没有自定义迁移管理的极端情况,这一点很重要,NOW 之前的所有迁移将并且必须在您的应用程序运行之前按顺序执行。如果您尝试针对当前的DbContext 执行去年的代码,我敢打赌它不会编译,更不用说成功执行了。

      如果您需要回滚迁移,也会出现类似问题...

      是的,必须手动执行我们在上下文中构建的一些自动操作有点烦人,但这是重要的部分:

      在 Schema 操作期间,我们的自动或触发逻辑几乎从不应用!

      “什么,你不能是认真的”我听到你说?想一想,如果您的自动逻辑正在为 createdmodified 等审计字段设置时间戳并将它们设置为 DateTime.Now(或类似的东西),那么这些值将对于我们正在迁移的这些数据是不正确的,在这种情况下,对于真正的审计记录,我们应该从原始 Customer 记录中复制时间戳和用户。

      如果你真的需要自动逻辑来运行 ALL 时间,即使在模式操作上,那么它也许是数据库触发器的候选者,是的,你可以做到这些,即使EF Code First,需要大量设置才能实现自动化,但您可以在migrationBuilder.Sql()

      中进行设置

      ...使用上下文会很棒,因为我在 SaveChangesAsync 中有有用的逻辑,我需要在 SQL 中复制这些逻辑。

      请不要在家里尝试这个...

      如果您想使用任何DbContext,则需要将其作为Seed 逻辑的一部分执行,该逻辑在迁移 之后运行。这意味着您必须通过两个步骤编写迁移代码:

      1. 迁移 1:创建新的架构元素
        • Seed中将必要的记录插入到新表中
      2. pm&gt; Update-Database
      3. 迁移 2:删除旧的架构元素
        • 删除 Seed 逻辑中之前的代码,因为现在模式元素已从模型中删除。
      4. pm&gt; Update-Database

      不要去那里!虽然这将在您的开发箱中工作,并且您可能没有超过 1 个部署,或者您正在积极开发您的生产环境数据库......无论你认为你有什么理由,它都不是一个有效的理由。

      这是一个绝对的 RED FLAG,作为一个单独的镜头,上述步骤在任何其他时间点都不能在单独的数据库架构上重复,因此除非您要同时在所有您的在从架构中删除元素之前同时使用数据库实例,它根本不起作用,并且:

      这不是一个好习惯,这充其量只是一个临时解决方案


      弃用

      当您有一个重要的架构更改涉及从架构中删除关键的表或字段但您需要使用您的 DbContext 运行复杂的逻辑或您的解决方案分布在一个这样一来,有很多依赖项需要对更改做出反应,但它们只能在新的架构元素可用后才能这样做。

      这遵循与上述 hacky 方法相同的原则,但需要更长的时间,并且可能会发布多个版本。

      这通常发生在微服务架构或基于 API 的解决方案中,其中部署不得干扰 3 或 4 个 9s SLA。在这种情况下,旧代码(之前的 DbContext)仍然需要能够利用数据库,因为新的上下文代码正在部署到您场中的每个服务器实例。

      添加新元素通常是向后兼容的,可以在需要时应用任何非破坏性更改。

      为了应用重大更改,有时我们不得不首先将这些类、方法或属性标记为[Obsolete]。然后,在您可以访问或控制的所有代码中,您可以根据需要删除引用或替换它们。

      例如,一个 EF 支持的 OData API 具有使用 Connected Serices(客户端代理)的客户端应用程序,应采用以下顺序安全地将更改推出到高 SLA 生产环境,比如说部署到 MS Azure。

      1. 禁用自动迁移,或将您的迁移策略配置为仅转发。

      2. 数据上下文:

        • 在架构中添加新表,
        • 将旧的标记为过时(如果您要删除任何)
          • 制作字段以删除 NULLABLE(如果它们还没有)
        • PM&gt; Add-Migration "Customer Addresses"
        • 编辑迁移脚本,包括 Sql() 逻辑以插入记录并清空或删除旧记录
        • 按照deprecation warnings(CS0612、CS0618、CS0619)进行必要的代码更改,以便您的数据上下文和相关代码为后续步骤做好准备。

      1.5。此时,PM&gt; Update-Database 通常是安全的。

      • 因此,您可以测试和部署到这一步,让团队的其他成员对弃用警告做出反应。
      1. API - 重新生成或操作您的 EdmModel 和 API 控制器以包含新元素,您还不能删除旧元素,如果您使用排除对 Obsolete 成员的引用的自动化技术,您可能必须禁用这些功能或以其他方式手动恢复以前的架构元素。

        • 这些元素需要保留,否则我们将破坏在客户端的 ODataClient 逻辑中执行的验证。

        • 添加特定逻辑以重新路由或至少处理任何尝试操纵过时字段的请求是个好主意,以减少以后架构更改的影响

          例如,在客户 PATCH 上,如果修改了 Address,您还将更新新的 Addresses 表中的关联/默认/前 1 条记录。

      2. 测试部署并再次端到端测试您的 API,您不应该引入任何破坏客户端的更改,还没有......如果您有,请回滚 API 部署。

      3. 通知客户端开发人员 API 已更改,描述新的架构元素,并就如何管理更改提供建议,并声明将在未来版本中删除的元素,并且他们应该做好准备他们的客户端模型可以在没有这些元素的情况下运行。

      4. 我们给客户足够的时间,这可能需要几年的时间来重新生成他们的客户端代理并更新他们的逻辑,因为每个人都阅读了文档,我们可以假设他们也做了必要的准备来删除对已弃用的引用架构元素。

      ...继续推出对您的架构和 API 的非破坏性更改和改进

      1. 在某个时候你决定是时候了,所以现在你可以从你的 EF 模型中删除元素

        • PM&gt; Add-Migration "Customer Remove Address Fields PREPARATION"
        • 编辑迁移脚本以注释掉 不要删除 删除架构元素的步骤!
          • !!!超级重要!!! - 旧的 SQL 需要有效,所以以前版本的 API 仍将执行,但我们需要 EF 来“认为”模型更改已应用于数据库. 我们注释掉生成的逻辑,因为我们可能希望稍后在完成架构清理时引用它。
        • PM&gt; Update-Database 通常是安全的,这一次是因为它不应该有任何更改,或者至少因为你删除了所有破坏性的。
      2. 重新生成或编码您的 EdmModel 和 API 控制器。这应该会删除之前引用的属性。

      3. 部署 API,再次通知客户端开发人员,这次注意重大更改...

        • 我们已经告诉他们这即将到来,所以没有人应该抱怨,如果需要,您可以回滚 API 部署或将一些旧版本保留在不同的 URL 或版本化路由中
      4. 客户端开发者可以在自己的时间再次做客户端代理过程。

      5. 一旦您确认没有客户端在使用旧代码或路由,并确认之前的 API 控制器逻辑没有实例在任何地方运行,那么我们可以完成清理过程。 .

      • `PM> Add-Migration Customer Remove Address Fields COMPLETION"

      • 这一次,我们需要再次运行逻辑来迁移或更新新的 Address 记录,因为旧客户端可能已经更新了 Obsolete 字段。

      • 移除架构元素的迁移逻辑不会出现在迁移脚本中,就 EF 而言,它们已被移除,但回到 第 6 步,因为我们已注释掉重大更改,您可以将这些迁移操作复制到此迁移步骤中,当然也可以取消注释。

        否则,您将不得不自己手动并小心地实现更改逻辑,以从架构中删除您想要的内容。

      1. PM&gt; Update-Database

      2. 重新发布您的 API,让新的上下文无处不在

      3. 该喝一杯了!
        我强烈建议在每一步结束时多喝几杯,这是一场漫长的比赛。我现在还可以提请您注意Ballmer Peak

      【讨论】:

        猜你喜欢
        • 2018-07-28
        • 2012-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-09
        相关资源
        最近更新 更多