向迁移添加扩展逻辑

时间:2021-03-25 19:50:44

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

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

在本例中,我有一个 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 创建一个 Customer,然后从 migrationBuilder.Sql 复制地址信息。< /p>

我曾经尝试过使用 SaveChangesAsync,但使用上下文会很棒,因为我在 * { box-sizing: border-box; margin: 0; padding: 0; } body { height: 100vh; display: flex; justify-content: center; align-items: center; background: * { box-sizing: border-box; background-color: #FFCCCC; margin: 0; padding: 0; } body { height: 100vh; display: flex; justify-content: center; align-items: center; background-color: #FFCCCC; } .slider { position: relative; width: 60%; overflow: hidden; } .images { display: flex; width: 100%; } .images img { height: 400px; width: 100%; transition: all 0.15s ease; } .images input { display: none; } .dots { display: flex; justify-content: center; margin: 5px; } .dots label { height: 20px; width: 20px; border-radius: 50%; border: solid #FFCCCC 3px; cursor: pointer; transition: all 0.15s ease; margin: 5px; } .dots label:hover {background: #fff;} #img1:checked ~ .m1 { margin-left: 0; } #img2:checked ~ .m2 { margin-left: -100%; } #img3:checked ~ .m3 { margin-left: -200%; } #img4:checked ~ .m4 { margin-left: -300%; }; } .slider { position: relative; width: 50%; overflow: hidden; } .images { display: flex; width: 100%; } .images img { height: 400px; width: 100%; transition: all 0.15s ease; } .images input { display: none; } .dots { display: flex; justify-content: center; margin: 5px; } .dots label { height: 20px; width: 20px; border-radius: 50%; border: solid #FFCCCC 3px; cursor: pointer; transition: all 0.15s ease; margin: 5px; } .dots label:hover {background: #FFCCCC;} #img1:checked ~ .m1 { margin-left: 0; } #img2:checked ~ .m2 { margin-left: -100%; } #img3:checked ~ .m3 { margin-left: -200%; } #img4:checked ~ .m4 { margin-left: -300%; } * {box-sizing: border-box} /* Container needed to position the overlay. Adjust the width as needed */ .container { position: relative; width: 57%; max-width: 300px; } /* Make the image to responsive */ .image { display: block; width: 100%; height: auto; } /* The overlay effect - lays on top of the container and over the image */ .overlay { position: absolute; bottom: 0; background: rgb(0, 0, 0); background: rgba(0, 0, 0, 0.5); /* Black see-through */ color: #f1f1f1; width: 100%; transition: .5s ease; opacity:0; color: white; font-size: 20px; padding: 20px; text-align: center; } /* When you mouse over the container, fade in the overlay title */ .container:hover .overlay { opacity: 1; } 中有有用的逻辑,我需要在 SQL 中复制。

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

3 个答案:

答案 0 :(得分:0)

我将创建一个 Address 类来保存您的三个属性。

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

然后您可以编写一个函数,该函数将遍历 Customer 对象的现有表以填充您的新表/模型。

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

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

答案 1 :(得分:0)

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

//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 的原因。

答案 2 :(得分:0)

tl;博士

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

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

<块引用>

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

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

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

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

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

中进行设置 <块引用>

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

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

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

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

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

这是一个绝对的红旗,孤立地做一次,上述步骤不能在任何其他时间点在单独的数据库模式上重复,所以除非你要同时在所有数据库上执行此迁移在从架构中删除元素之前的同一时间数据库实例,它根本不起作用并且:

实践不是一个好习惯,这充其量只是创可贴


弃用

当您进行重大架构更改(涉及从架构中删除关键的表或字段,但您需要使用 DbContext 运行复杂逻辑或您的解决方案是分布式的)时,有一个标准的软件工程解决方案在某种程度上,有许多依赖项需要对更改做出反应,但它们只能在新架构元素可用后才能这样做。

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

<块引用>

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

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

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

例如,EF 支持的 OData API 具有使用连接服务(客户端代理)的客户端应用程序,应采用以下序列安全地将更改推出到高 SLA 生产环境,假设部署到 MS Azure。

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

  2. 数据上下文:

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

1.5。在这个时间点PM> Update-Database通常是安全的。

  • 这样您就可以测试和部署仅到这一步,让您的团队的其他成员对弃用警告做出反应。
  1. API - 重新生成或操作您的 EdmModel 和 API 控制器以包含新元素,如果您使用排除对 {{1 }} 成员,您可能必须禁用这些功能或以其他方式手动重新设置以前的架构元素。

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

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

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

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

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

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

... 继续对您的架构和 API 进行不间断的更改和改进

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

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

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

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

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

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

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

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

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

  1. PM> Update-Database

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

  3. 该喝一杯了!
    我强烈建议在每一步结束时多喝几杯,这是一场持久战。我现在还请您注意 Ballmer Peak