原文链接:https://www.thereformedprogrammer.net/handling-entity-framework-core-database-migrations-in-production-part-2/
作者:Jon P Smith
在生产中运用EFCore的模式实战
这是使用EF Core迁移数据库的系列文章中的第二篇。本文着眼于将迁移应用于数据库,并从第1部分开始,该部分介绍了如何创建迁移脚本。如果您还没有阅读第1部分,那么本文的某些部分将毫无意义,因此这里是第1部分的快速回顾。
•可以将两种类型的迁移应用于数据库:•添加新的表,列等,称为不间断的更改(简单)。•更改列/表并需要复制数据,这称为重大更改(困难)。•将迁移应用于数据库的主要方法有两种•使用EF Core迁移功能•使用EF Core创建迁移,然后手动修改迁移。•使用第三方迁移构建器在C#中编写迁移。•使用SQL数据库比较工具比较数据库并输出SQL更改脚本。•通过复制EF Core的SQL编写自己的SQL迁移脚本。
因此,既然您现在知道如何创建迁移脚本,那么我将研究可以将迁移应用于生产数据库的不同方式,以及这些方式所具有的利弊。
TL; DR –内容摘要
注意:单击链接可直接转到涵盖该点的部分。
•您具有影响迁移方法可以使用的应用程序的类型。•您必须考虑可能发生的错误并制定计划。•有四种方法可以将迁移应用于数据库•在启动时调用context.Database.Migrate()Very Easy,但是存在一些严重的问题,限制了它的实用性。•通过控制台应用程序调用context.Database.Migrate()-Easy,并且效果很好,尤其是在部署管道中•将EF Core迁移输出为SQL脚本并在目标数据库上执行该脚本 -Hard,但是却能提供很好的控制。•使用数据库迁移应用程序工具来应用您自己的SQL脚本 -Hard,但是您却可以很好地控制。•应用迁移的三个不同级别。•在迁移数据库时停止应用程序是最安全的选择,但并非总是可能的。•在应用程序运行时,可以将某些(但不是全部)不间断的更改应用于数据库。•对于连续服务应用程序(7*24小时运行的服务),要应用重大更改需要五个步骤。
场景分析–您的产品是哪种应用程序?
在第1部分中,我们着重于创建“有效”的迁移,以及迁移是不间断的变更还是重大变更(请参阅本文开头的快速定义,或第1部分中的此链接。
现在,我们正在考虑将迁移应用于数据库,但是我们拥有的选项取决于正在访问数据库的一个或多个应用程序。这是您需要考虑的问题。
1.是只有一个应用程序访问该数据库,还是您的应用程序是横向扩展的Web应用程序,即,同时运行多个版本的应用程序。如果您的应用程序是横向扩展,则将删除其中一个选项。2.您可以在将迁移应用到数据库时停止应用程序,还是您的应用程序提供7*24小时连续服务?在应用重大变更方面,更新连续服务应用程序会带来一些挑战。
在迁移生产数据库时,有点偏执是可以的。
正如我在第1部分末尾所说的那样-当您将迁移应用于生产数据库时,最恐怖的部分到来了。更改包含关键业务数据需求(需求!)的数据库,请仔细计划和测试。您需要考虑如果(何时!)迁移因错误而失败时该怎么办。
在考虑应用迁移的不同方法时,您应该脑海中浮现“如果有错误会发生什么?”。这可能会促使您采用更复杂的迁移方法,因为它更易于测试或还原。我不能为您提供规则或建议,因为每个系统都不同,但是对故障有点偏执并不是一件坏事。我应该让您构建一个更健壮的用于迁移应用程序及其数据库的系统。
第2部分:如何将迁移应用于数据库。
下面的列表提供了将迁移应用于数据库的不同方法。我列出了EF Core案例的三个选项:第一个是最简单的,但是它有其他两个选项所没有的限制。SQL迁移没有实际限制,但确实需要数据库迁移应用程序工具才能以正确的顺序应用SQL脚本。
这是您可以应用迁移的方法列表。
1.EFCore迁移1.在启动时调用context.Database.Migrate()2.通过控制台应用程序或管理命令调用context.Database.Migrate()3.将迁移输出作为SQL脚本输出,然后在目标数据库上执行该脚本。2.SQL迁移1.使用数据库迁移应用程序工具。
最后,如何应用迁移取决于迁移类型(中断或不中断)和要更新的应用程序类型(单个应用程序,并行运行的多个应用程序或必须停止的应用程序)。这是所有这些排列的图表。
外部的深蓝色表示可以在所有情况下都应用SQL迁移,而内部较浅的方框表示可以在其中添加不同类型的EF Core迁移。以下是有关该图的一些澄清说明:
•该图显示了标准EF迁移和手工修改的EF迁移,但是当我谈论应用迁移时,两者之间没有区别-我们很简单地应用EF Core迁移。•图中的“五个阶段的应用程序更新”红色框表示您需要对无法停止的应用程序进行重大更改所需要的复杂阶段。我将在文章末尾介绍。
现在,我将详细介绍应用迁移的每种方式。
1a。在启动时调用context.Database.Migrate()
到目前为止,这是应用迁移的最简单方法,但是它有一个很大的局限性–您不应同时运行Migrate方法的多个实例。如果横向扩展Web应用程序,则可能会发生这种情况。引用安德鲁·洛克(Andrew Lock)的话:“
我们不能保证这会给您带来麻烦,但是除非您非常谨慎地确保幂等更新和错误处理,否则您很可能会陷入困境
” –请参阅他的帖子的这一部分“ 在ASP.NET Core中的应用启动时运行异步任务[1] ”。
好处 | ·相对容易实现(请参阅提示) ·确保在应用程序运行之前数据库是最新的。 |
坏处 | ·不得并行运行两个或多个Migrate方法。·如果迁移有错误,则您的应用程序将不可用。·难以诊断启动错误 |
局限性 | 不适用于连续服务系统 |
提示 | 我非常喜欢Andrew Lock的文章中的在启动时运行迁移的选项[2]。我在一些使用内存数据库的演示系统中使用了类似的方法,这些数据库需要初始化(请参见本示例[3]) |
我的建议 | 如果您正在运行单个Web应用程序或类似的Web应用程序,并且可以在没有人使用它的情况下更新系统,那么这可能对您有用。我没有像我使用的许多系统那样使用横向扩展。 |
1b。通过控制台应用程序或管理命令调用context.Database.Migrate()
如果您不能并行运行多个Migrate方法,那么确保此方法的一种方法是在设计为仅执行Migrate方法的独立应用程序内调用Migrate方法。您可以在主Web应用程序解决方案中添加一个控制台应用程序项目,该项目可以访问DbContext并可以调用Migrate。您既可以自己运行它,也可以让您的部署系统运行它(EF6.x用户注意–这等效于运行Migrate.exe,但其中已编译应用程序dll)。
好处 | ·它适用于所有情况。·与部署系统配合良好。 |
坏处 | 还有更多工作。 |
局限性 | –无–,但请注意持续进行的五阶段应用程序更新 |
提示 | 如果您的控制台应用程序使用连接字符串来定义要将迁移应用到哪个数据库,那么它将更易于在部署管道中使用。 |
我的建议 | 如果您具有部署管道,那么这是一个不错的选择,因为您可以在部署过程中执行控制台应用程序。如果您是手动应用迁移,则有命令Update-Database。 |
1c。将EF Core迁移转换为脚本并将其应用于数据库
通过使用脚本迁移命令EF Core会将特定的迁移或默认情况下的所有迁移转换为SQL脚本。然后,您可以使用可以在要更新的特定数据库上执行SQL的方法来应用此方法。您可以在SQL Server Management Studio中手动执行SQL ,但是通常您的发布管道中有一些内容可以在适当的时间执行。
好处 | ·它适用于所有情况。·与可以使用SQL脚本的部署系统一起很好地工作。·您可以在运行SQL之前先查看它,看看它是否正常。 |
坏处 | ·比控制台应用程序(1b)更多的工作 ·您需要一些应用程序将脚本应用于正确的数据库。 |
局限性 | –无–,但请注意持续进行的五阶段应用程序更新 |
提示 | SQL包含用于更新迁移历史记录的代码,但是您必须在Script-Migration命令中包括idempotent选项,以获取阻止两次应用迁移的检查。 |
我的建议 | 如果您想使用EF Core的Migrate方法,那么我建议您使用控制台应用程序1b。它与使用脚本一样安全,并且执行相同的工作。但是,如果您的管道已经可以使用SQL更改脚本,那么这非常适合您。 |
2a。使用迁移工具应用SQL脚本
如果创建了一系列SQL迁移脚本,则需要以下步骤:a)以正确的顺序应用它们,b)仅应用一次。EF Core的迁移包含执行“正确顺序”和“仅一次”规则的代码,但是当我们编写自己的迁移脚本时,我们需要一个可以提供这些功能的工具。
我和其他许多人使用了一个名为DbUp的开源库,该库提供了这些功能(以及更多功能),还支持多种数据库类型。我按字母顺序排列迁移脚本,例如“ Script0001 –初始迁移”,“ Script0002 –添加种子数据”以供DbUp应用。就像EF Core迁移一样,DbUp使用一个表来列出哪些迁移已应用到数据库,并且仅在该表中没有迁移时才应用。
还可以使用其他迁移工具,例如Octopus Deploy和各种RedGate工具(但我没有使用过它们,因此请检查它们是否具有正确的功能)。
好处 | ·它适用于所有情况。与部署系统配合良好。 |
坏处 | ·您必须管理脚本。 |
局限性 | –无–,但请注意持续进行五阶段应用程序更新 |
* 提示 * (适用于DbUp) | ·我制作了一个控制台应用程序,该应用程序接受连接字符串,然后运行DbUp,因此可以在部署管道中使用它。·为了进行测试,我使运行DbUp的方法在“仅以调试模式运行”单元测试中可用于我的单元测试程序集,该方法使用我的CompareEfSql工具正确迁移了本地数据库(请参阅本系列第1部分中有关测试迁移的部分。 |
我的建议 | 使用EF Core的项目上使用这种方法。 |
应用程序和应用程序迁移
将迁移应用于数据库时,可以停止应用程序,或者在某些情况下可以在迁移运行时应用迁移。在本节中,我将介绍为您提供的不同选项。
1.在迁移数据库时停止应用程序
这是最安全的选项,可与重大更改和不中断更改一起使用,但是您的用户和您的业务可能并不那么满意。我称其为“维护站点”。在“站点关闭”方法中,您不想在用户输入数据或完成订单时停止应用程序。这就是您或您的公司获得不良声誉的方式。
我早在2015年就遇到了这个问题,并且我创建了一种方法来警告人们该网站将要关闭,然后停止除管理员以外的所有人员访问该应用程序。我之所以选择这种方法,是因为对于正在使用的Web应用程序,此方法比支持破坏性更改同时保持Web应用程序运行的开销要小(我将在稍后介绍对连续服务应用程序进行中断)。通常在周末和晚上,您可能会遇到所使用服务的“此站点已关闭维护”。
注意:我写了一篇名为“ 如何使ASP.NET MVC网站“为了维护而停机 ””的文章,您可能希望看一下-该代码是针对ASP.NET MVC5的,因此需要一些工作才能使其正常工作。.NET Core,但该想法仍然有效。
在应用程序运行时应用不间断的迁移
从理论上讲,通过不间断的更改,您可以在旧应用程序运行时将其应用于数据库,但是有些问题可能会让您失望。例如,如果您添加了一个没有SQL默认值且不知道该新列的旧软件的新的非空列,并尝试插入新行,则您会收到一条SQL错误,因为旧软件没有提供了非空列的值。
但是,如果您知道不间断的迁移没有问题,那么在旧应用程序运行时应用迁移将为您的用户提供连续的服务。有多种方法可以执行此操作,具体取决于您选择了哪种迁移应用程序方法,想到的就是Azure的暂存槽(已经存在了很长时间)和更新的Azure Pipelines。
将重大更改应用于连续运行的应用程序:五阶段的应用程序更新。
最困难的工作是对不断运行的应用程序进行重大更改。在显示不同方法的图表中,右上方会显示一个名为“五阶段应用程序更新”的红色框。该名称来自以下事实:您需要分阶段迁移,通常为五个阶段,如下图所示。
注意:安德鲁·洛克(Andrew Lock)称赞我在上一节中描述的“添加不可为空的列”问题可以分三个阶段处理:a)添加新列但可为空,b)部署已知该列的新软件,以及c)将列更改为不可为空。
这是我的《EFCore》一书的第11.5.3节中的图表,该图显示了添加重大更改所需的五个阶段,这些更改将现有的CustomerAndAddress表分为两个表,Customers和Addresses。
如您所见,这样的更新创建起来很复杂,应用起来也很复杂,但这就是运行连续系统的成本。这五个阶段没有任何真正的替代方案,除了您永远不要对连续运行的系统应用重大更改(我听说有人说这是他们的方法)。
注意:我在我的书“ Entity Framework Core in Action[4] ”的11.5.3节中介绍了持续的,五个阶段的应用程序更新,您还可以在Neil Ford的“ Building Evolutionary Architectures ” 一书的第5章中找到有关此内容的内容。等。
结论
如果数据库中的数据和服务的可用性对组织很重要,那么您必须认真对待数据库迁移。在第1部分中,我介绍了创建迁移脚本的不同方法,并且本文介绍了如何将这些迁移应用于生产数据库。本系列文章的目的是为您提供各种选择,以及它们的优缺点,以便您可以就如何处理迁移做出明智的决定。
就像我在第一篇文章中所说的那样,我与EF迁移的第一个磨合是使用EF6。我非常了解EF6,并且写过《 Entity Framework Core in Action》一书,[5]我对EF Core的了解甚至更好。围绕迁移从EF6到EF Core的变化代表了EF Core中整个方法的变化。
EF6进行了很多“魔法”操作,使其更易于使用- 启动时自动迁移就是其中之一。问题是,当EF6的“魔法”效果不佳时,很难对其进行梳理。EF Core的迁移方法是由您决定如何在何处以及如何使用它-没有自动的“魔法”。EF Core迁移的许多其他小变化来自于聆听EF4到6的用户。
因此,在生产数据库上的迁移令人恐惧。我已经为您提供了一些有关选项的见解,但这仅是更改生产数据库的最低要求。需要根据需要添加备份,策略,产品前测试和部署管道,以构建可靠的系统。
祝你能享受编码的快乐!
References
[1]
在ASP.NET Core中的应用启动时运行异步任务: https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-part-1/[2]
选项: https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-part-1/#4-manually-running-tasks-in-program-cs[3]
本示例: https://github.com/JonPSmith/EfCore.GenericServices/blob/master/RazorPageApp/Program.cs[4]
Entity Framework Core in Action: http://bit.ly/2m8KRAZ[5]
,: http://bit.ly/2m8KRAZ