.NET CORE EF数据库迁移,以及迁移命令详解。code first。在不删除表的情况下保持同步 电脑版发表于:2019/12/22 9:52 ### .NET CORE EF 数据库迁移需要的依赖 ``` Install-Package Microsoft.EntityFrameworkCore.SqlServer -version 3.1.1 Install-Package Microsoft.EntityFrameworkCore.Tools -version 3.1.1 Install-Package Microsoft.EntityFrameworkCore.Design -version 3.1.1 ``` 或者使用ItemGroup的方式安装依赖 ``` <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> ``` 也不一定全部都要用到,根据实际情况来。Tools 库里边其实就包含了Design库 ###以前的迁移命令 Enable-Migrations –EnableAutomaticMigrations ![](https://img.tnblog.net/arcimg/aojiancc2/38bfcc407d7540f2945a0b0e95a63f68.png) 已经过时不能使用了 ###新的命令 Add-Migration InitialCreate <img src="https://img.tnblog.net/arcimg/aojiancc2/4e1fd1dc516047219378c9e1d943c083.png" width = "400px"/> <img src="https://img.tnblog.net/arcimg/aojiancc2/6ca4e4fce9d54e8cbefbd8a2ad79f723.png" width = "400px"/> 一般第一次执行的初始化迁移,都是创建表,这些。还会生成数据库快照文件XXModelSnapshot.cs。 #####更新到数据库的命令 Update-Database [迁移名称] 后边的迁移名称是可选的,可以接迁移名称,例如: Update-Database AddColumn #####上面都是比较简单的描述了一下,留下两个问题 1:怎么可以跳过某个迁移步骤,比如数据库已经存在的情况下,初次执行迁移命令会生成对数据库的创建,如果不跳过,会出现xx表已经存在的错误 2:Update-Database [迁移名称]接或者不接有什么不同 <br/> ### 迁移命令详解 #### Add-Migration 迁移名称 执行一次迁移,创建迭代版本,相当于做一个记录,生成迁移代码。 如果该命令是首次创建,则会在执行命令的项目下生成Migrations文件夹, 创建数据库快照文件XXModelSnapshot.cs,以及本次迭代文件。如图 <img src="https://img.tnblog.net/arcimg/aojiancc2/4e1fd1dc516047219378c9e1d943c083.png" width = "400px"/> <img src="https://img.tnblog.net/arcimg/aojiancc2/6ca4e4fce9d54e8cbefbd8a2ad79f723.png" width = "400px"/> 添加迁移时,EF会通过将数据模型与快照文件进行对比来确定已更改的内容。 使用get-help Add-Migration -detailed,可以查看到Add-Migration的使用详情。 如果是第一次执行迁移命令会生成对表的创建和删除,UP方法就是对表的创建,Down方法就是对表的删除 ![](https://img.tnblog.net/arcimg/aojiancc2/367a951e62af4e2eaf4791890cf0f999.png) 所以如果数据库中有表在执行Update-Databas命令的话会报错,提示表已经存在 ![](https://img.tnblog.net/arcimg/aojiancc2/15e6881f23344b3fad559c84efd33306.png) 所以如果你数据库已经在前面创建好了,可以把初次执行迁移命令生成的版本删除掉。不然就算你执行Update-Database的时候接一个迁移名称,还是会报错,因为初始化那一步你没有执行成功,就算你接迁移名称还是会去执行没有执行过成功的迁移文件,所以还是会报错,目前还没有找到一个命令可以只执行一个指定的迁移名称。估计他要这样设计也是为了让迁移记录和数据库保持一致性。 其实要解决重复创建表的问题,也不一定要删除首次创建的迁移文件,干掉迁移文件里边的Up和Down内容也是可以的,这样执行Update-Database就不会有问题了,因为不会重新在去创建一次表。(但是最好不要手动删除迁移文件,因为快照文件里边还有记录,直接手动删除可能也会造成迁移记录的错乱,所以要撤销迁移,或者删除迁移最好使用命令Remove-Migration来) ![](https://img.tnblog.net/arcimg/aojiancc2/42830d5735334fbfb958e637038adebe.png) 当然还有一种方法,就是向添加迁移日志表中添加想要跳过的记录,因为ef判断某一个迁移是否执行,是根据迁移表EFMigrationsHistory来的。比如你想跳过20211217011025_InitialCreate这一步,向迁移表中添加这条记录就行, ![](https://img.tnblog.net/arcimg/aojiancc2/1e6f1f5bc95040b58ee31a9d9e0831a0.png) 所以在执行更新数据库的时候想要跳过某步,这里说了三种方法。 1:删除对应的迁移文件 2:删除或者注释掉迁移文件里边的内容,本质和第一步其实是一样的 3:向迁移表__EFMigrationsHistory中添加需要跳过的迁移步骤 **add-migration --context XXXDbContext** 一个程序集下面存在多个DbContext 指定DbContext进行迁移 <br/> #### Update-Database [迁移名称] 更新到数据库。该命令更新所有未执行的迭代版本。调用的是迭代文件中的Up方法 Update-Database [迁移名称],后边也可以接迭代名称,就针对性执行某个迁移了 接名称完整的写法:Update-Database -Migration 迁移名称 执行成功可以在数据库看到执行的日志 ![](https://img.tnblog.net/arcimg/aojiancc2/ab465b9401d546afb359c5126a9694ec.png) 输入get-help Update-Database -detailed,可以查看到Update-Database的使用详情。 上面说过,就算你接一个迁移名称,并不是只执行那一个迁移版本,前面如果有执行失败的,就算你接了一个迁移名字,还是会执行那个失败的版本。 **那么问题就来了,既然接不接迁移名称都会执行前面没有执行过的或者执行失败的,那么接名称的意义在哪里呢?都是根据迁移日志来决定执不执行。** 其实是这样的在你执行迁移命令接名称的时候,在这个迁移文件,以及迁移文件之前的都要成功执行,相当于这个名称是指定一个目标点,到这个点截至,如果不接迁移名称就是所有的迁移文件执不执行都是根据日志来的,而迁移日志又是执行成功才会添加的,所以就是说必须要保证到这个迁移文件为止的全部要成功才行。 所以如果你指定的迁移名称是最后一个没有执行的版本,那么接不接都一样,因为执行的截至点都是那一个,只有当有多个迁移文件没有执行的时候接不接迁移名称才有区别。 他并不会跳过中间没有执行的,或者执行失败的,而是到这个名称为止的所有迁移文件都要保证执行成功。 ![](https://img.tnblog.net/arcimg/aojiancc2/eb0b20f93a834d018ffdb34587f5c3bf.png) 比如我们执行Update-Database -Migration AddColumn_LL,它会执行到这个文件为止,也就是检查前三个文件有没有被成功执行,没有成功执行或者没有执行过的都会执行。但是他并不是执行AddColumn_PP这个文件,也就是说它后面的不会管。 如果执行的是Update-Database -Migration AddColumn_PP,就会检查这里的所有迁移文件了,由于这里是时间点最后的一个迁移文件,所以接不接这个名称都一样。 **接名称还有一个意义就是回归到某个版本**,当然要回归到某个版本,前提是这个版本成功应 用过。比如上图的例子,假如所有版本都成功执行了,我想要回归到AddColumn这个版本就可以执行,Update-Database -Migration AddColumn,想要回归到AddColumn_LL版本,就执行Update-Database -Migration AddColumn_LL即可。这个回归版本的意思就是让数据库保持到这一步执行后的效果。所以你执行的是最后一步的话Update-Database -Migration AddColumn_PP,相当于就是最新的迁移版本了,并不是说回退到AddColumn_PP版本没有执行之前的版本,而是回到这个版本执行成功的版本。 <br/> #### Remove-Migration 回滚命令,回滚最后一次的操作,撤销本次更新的内容 撤销本次更新的内容,调用的是Down方法中的内容 回滚最后一次的操作,也就是当前的操作。官方解释:Removes the last migration. 查看命令详情 ``` get-help Remove-Migration -detailed ``` **执行的效果:** ![](https://img.tnblog.net/arcimg/aojiancc2/202ba6817cf440ff8b7a426ab3d8868e.png) 它会把这次迁移的迁移文件删除掉,而且会把快照的修改也还原。所以如果执行了迁移又想还原这次迁移就可以使用这个撤销命令。这次测试的是迁移还没有更新到数据库的情况。 **如果执行Remove-Migration 报错:has already been applied to the database** ![](https://img.tnblog.net/arcimg/aojiancc2/4d861a10a5cd48ffbd06bf7403567fe8.png) 他说已经成功更新到了数据库,给你做一个提示。如果你要确定更新,可以使用如下命令即可。 ``` Remove-Migration -Force ``` **Remove-Migration -Force:回滚最近的一次迁移记录** 会把本次生成的迁移文件删除掉,还有后缀为ModelSnapshot的快照文件也撤销,因为这里边也会记录操作内容。<font color=red>所以如果这次迁移执行部分成功,部分失败的话,执行这个命令不会让更新数据库的还原,只会删除迁移文件,所以数据库的操作你可能需要手动去数据库处理一下</font>(比如附表存在的情况下添加外键,因为数据问题造成了表添加成功了,外键没有添加成功的情况,自己在数据库处理一下就行也很加拿大)。 当然你可以先执行Update-Database -Migration:0 ,删除表结构 然后再执行:Remove-Migration (删除迁移文件)。<font color=red>当然如果迁移已经应用到了数据库这个命令要特别慎用,他好像某些情况下会把你前面执行的表都删除掉。</font>所以完全不建议这样使用。 ### 迁移记录,迁移文件和数据库不一致的情况下。如果在不删除表的情况下保持同步。(可以参考下面的快照文件同步法,可能还要简单一点) tn2>正常情况下code first都是通过代码生成数据库,迁移文件,迁移记录,快照文件都应该和数据库一致,但是有些情况下,是先写了数据库,然后在写的代码,这样就会造成,迁移记录和数据库不一致了。这种情况下,如果后面又增加了表,或者更改了字段等等,在去执行数据库迁移的时候就会出现问题。(比如团队中有些人不会使用迁移命令乱整-.-) 比如数据库收手动加了一个role表,不是通过迁移文件去创建的,这个时候执行数据库更新就会提示,role表已经存在了,因为他没有在迁移文件中记录到。 **解决方法:** 先让role表的创建保持迁移文件和数据库一致后,在去做其他修改。但是如何保持一致呢,role表明明就不是迁移文件生成的数据库,而是手动在数据库中创建的表。其实很简单,我们上面知道原理后就可以解决这种问题了,不用在去把数据库干掉在重新执行迁移命令了。 先把后面相关修改的注释掉,比如在role表同步只会想要的操作,都注释掉,就是要让当然实体的内容和数据库的表保持一致。然后把快照文件删除掉,执行一次迁移Add-Migration -fb(名字随便取,这里我写的tb就是同步的简拼),然后这个时候我们就不去执行Update-Database更新数据库了,如果执行还是会报错的,因为数据库表已经存在了,我们的目的就是让迁移文件和数据库保持一致,所以我们要让ef认为我们这个命令是成功执行了的,我们只需要在数据库迁移记录表中手动添加一条记录即可。 ![](https://img.tnblog.net/arcimg/aojiancc2/bc124e4a2b0d4db59e9e2f1615102110.png) 然后就ok了,后面正常使用即可。 tn2>为什么要删除快照呢?如果不删除快照的话,执行后面的迁移估计没有任何内容的,因为创表这些操作已经记录在快照文件里边的。这就是为什么要做撤销操作,不能手动删除迁移文件,如果快照里边还有操作记录哇,你手动删除迁移文件,如果在执行一次Add-Migration它会什么都没有改变的。所以要像撤销操作要使用Remove-Migration命令或者Remove-Migration -Force。 -。-录了一个讲解的视频,后面才发现没有声音 ### 数据库迁移解决方法之-快照文件同步法 比如添加了迁移记录然后更新到数据库的时候报错:重复创建字段,PreviewUrl。 **问题分析。** 不知道什么原因造成快照和数据库不一致,数据库里边文件管理表有PreviewUrl字段,但是快照里边没有PreviewUrl字段,所以执行迁移的时候就会这个字段已经有了导致迁移失败,所以在快照里边增加去添加一下这个字段,就好了 ![](https://img.tnblog.net/arcimg/aojiancc2/1d5c7ee6f2084b4c9c8ad07456a7bb0b.png) 数据库和代码就同步了。在执行后面的操作就不会有问题了。 知道原理了,啷个方便啷个操作就行了,一句话。在进行迁移操作数据库的时候,要保证快照文件和数据库对应。 其实正常操作的话他肯定都是对应的哇,各种骚操作导致没有对应上也没有关系,[狗头]手动改一下就完事,怕的是不懂这个原理,改不了就麻烦了。知道原理了随便操作,不慌。 真正生成迁移记录里边的操作是根据快照和数据比较的,有什么改变就生成对应的操作。所以如果快照文件内容和数据库不一致的时候,在去生成一个数据库库迁移操作就会问题,比如上面提到的重复创建字段PreviewUrl的问题。 ### 简单的总结,快照文件介绍 ef的数据库迁移,主要就是几个命令的使用,但是想要用好,需要对里边的原理比较了解。其实原理核心点就是迁移记录,迁移快照,迁移表,把这些搞清楚了,就可以很灵活的使用ef的数据库迁移了。 上面对迁移,和迁移记录表介绍得比较多。数据库快照得话,其实就是当前的一个数据的创建记录,要和数据库保持同步,才能更好的去修改数据库。 **比如像快照的这样一段** 记录了表的导航属性,外键等信息,当然这个文件其他地方也有这个表的字段这些信息记录。 ![](https://img.tnblog.net/arcimg/aojiancc2/12b5db1d801745929d36ffa326425285.png) 执行迁移文件的时候就是根据这个快照文件和你的代码进行比较,才知道你进行了哪些修改,然后在生成对应的操作数据库的语句的迁移记录文件,执行update-databases的时候在根据迁移文件和数据库的迁移记录比较,在去操作数据库,整体的一个原理和执行流程大概就是这样。想要更好的理解这些原理,可以自己创建一个简单的项目,然后去修改实体,看看迁移文件,迁移记录表,快照的变化即可。 执行迁移命令的时候,除了快照和实体类对比,还会受到数据库版本,创建数据库的设置影响,比如开始连接点win下面的mysql数据库换到linux下的docker的MySQL,有些类型会不同,ef也会检查到,比如以前连接的是在win下面的mysql时间类型是datetime类型,换到了linux下的docker的MySQL时间类型执行迁移的时候就会全部变成了datetime(6)了