EF Core 6执行add-migration时重复创建已存在数据表问题
问题现象
先前迁移中已经通过migrationBuilder.CreateTable完成了AppDespesaExtraOrcamentaria表的创建,仅调整实体的唯一索引配置后执行add-migration命令,新生成的迁移文件没有按预期输出索引增量变更语句,反而重复生成了该表的完整建表逻辑。
先前迁移中的建表代码
migrationBuilder.CreateTable( name: "AppDespesaExtraOrcamentaria", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Exercicio = table.Column<int>(type: "int", nullable: false), CodigoReduzido = table.Column<int>(type: "int", nullable: false), Conta = table.Column<long>(type: "bigint", nullable: false), Complemento = table.Column<short>(type: "smallint", nullable: false), Descricao = table.Column<string>(type: "varchar(50)", nullable: true), FundoId = table.Column<int>(type: "int", nullable: false), FornecedorId = table.Column<int>(type: "int", nullable: false), FonteDeRecursoId = table.Column<int>(type: "int", nullable: true), CodigoDeAplicacaoId = table.Column<int>(type: "int", nullable: true), DestinacaoRecursoId = table.Column<int>(type: "int", nullable: true), FonteDeRecursoSICONFIId = table.Column<int>(type: "int", nullable: true), ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true), LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true) }, constraints: table => { table.PrimaryKey("PK_AppDespesaExtraOrcamentaria", x => x.Id); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppCodigoDeAplicacao_CodigoDeAplicacaoId", column: x => x.CodigoDeAplicacaoId, principalTable: "AppCodigoDeAplicacao", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppDestinacaoRecurso_DestinacaoRecursoId", column: x => x.DestinacaoRecursoId, principalTable: "AppDestinacaoRecurso", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFonteDeRecurso_FonteDeRecursoId", column: x => x.FonteDeRecursoId, principalTable: "AppFonteDeRecurso", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFonteDeRecursoSICONFI_FonteDeRecursoSICONFIId", column: x => x.FonteDeRecursoSICONFIId, principalTable: "AppFonteDeRecursoSICONFI", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFornecedor_FornecedorId", column: x => x.FornecedorId, principalTable: "AppFornecedor", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFundo_FundoId", column: x => x.FundoId, principalTable: "AppFundo", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateIndex( name: "IX_AppDespesaExtraOrcamentaria_TenantId_Conta", table: "AppDespesaExtraOrcamentaria", columns: new[] { "TenantId", "Conta" }, unique: true, filter: "[TenantId] IS NOT NULL");
异常生成的新迁移代码
migrationBuilder.CreateTable( name: "AppDespesaExtraOrcamentaria", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Exercicio = table.Column<int>(type: "int", nullable: false), CodigoReduzido = table.Column<int>(type: "int", nullable: false), Conta = table.Column<long>(type: "bigint", nullable: false), Complemento = table.Column<short>(type: "smallint", nullable: false), Descricao = table.Column<string>(type: "varchar(50)", nullable: true), FundoId = table.Column<int>(type: "int", nullable: false), FornecedorId = table.Column<int>(type: "int", nullable: false), FonteDeRecursoId = table.Column<int>(type: "int", nullable: true), CodigoDeAplicacaoId = table.Column<int>(type: "int", nullable: true), DestinacaoRecursoId = table.Column<int>(type: "int", nullable: true), FonteDeRecursoSICONFIId = table.Column<int>(type: "int", nullable: true), ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true), CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true), LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true) }, constraints: table => { table.PrimaryKey("PK_AppDespesaExtraOrcamentaria", x => x.Id); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppCodigoDeAplicacao_CodigoDeAplicacaoId", column: x => x.CodigoDeAplicacaoId, principalTable: "AppCodigoDeAplicacao", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppDestinacaoRecurso_DestinacaoRecursoId", column: x => x.DestinacaoRecursoId, principalTable: "AppDestinacaoRecurso", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFonteDeRecurso_FonteDeRecursoId", column: x => x.FonteDeRecursoId, principalTable: "AppFonteDeRecurso", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFonteDeRecursoSICONFI_FonteDeRecursoSICONFIId", column: x => x.FonteDeRecursoSICONFIId, principalTable: "AppFonteDeRecursoSICONFI", principalColumn: "Id"); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFornecedor_FornecedorId", column: x => x.FornecedorId, principalTable: "AppFornecedor", principalColumn: "Id", onDelete: ReferentialAction.Restrict); table.ForeignKey( name: "FK_AppDespesaExtraOrcamentaria_AppFundo_FundoId", column: x => x.FundoId, principalTable: "AppFundo", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateIndex( name: "IX_AppDespesaExtraOrcamentaria_TenantId_Conta_Complemento_Exercicio", table: "AppDespesaExtraOrcamentaria", columns: new[] { "TenantId", "Conta", "Complemento", "Exercicio" }, unique: true, filter: "[TenantId] IS NOT NULL"); migrationBuilder.CreateIndex( name: "IX_AppDespesaExtraOrcamentaria_TenantId_Exercicio_CodigoReduzido", table: "AppDespesaExtraOrcamentaria", columns: new[] { "TenantId", "Exercicio", "CodigoReduzido" }, unique: true, filter: "[TenantId] IS NOT NULL");
本次操作仅修改了表的唯一索引配置,预期生成删除旧索引、创建新索引的增量语句,实际却输出了完整建表逻辑。
问题原因
核心原因是EF Core的迁移快照与实际已应用的迁移状态不一致,EF Core判定该表尚未创建,因此生成了完整建表语句,常见触发场景有三类:
- 手动修改过历史迁移文件的建表/建索引代码,没有同步更新对应迁移的Model快照
- 多分支合并时迁移文件冲突未正确解决,导致快照丢失了该表的已创建记录
- 实体对应的表名映射配置被意外修改,EF Core判定当前模型中的实体对应了一张全新的未创建表
解决方法
按以下步骤操作即可彻底修复:
- 先删除这次异常生成的迁移文件,同时删除对应文件名末尾带
.Designer.cs的快照附属文件 - 检查
DbContext中该实体的表名映射配置,确认ToTable("AppDespesaExtraOrcamentaria")配置没有改动,和历史迁移中的表名完全一致 - 打开Migrations目录下的*[你的DbContext名称]ModelSnapshot.cs*文件,搜索
AppDespesaExtraOrcamentaria,确认快照中是否存在该表的完整定义:- 如果不存在,找到最近一次创建该表的迁移文件,将其中的表结构定义、外键、索引定义同步复制到Model快照中,和其他已存在表的格式保持一致
- 如果存在,检查表名、列名、主键配置是否和历史迁移中的定义完全匹配,修正不一致的配置
- 重新执行
add-migration命令,此时生成的迁移文件就只会包含索引调整的增量逻辑:删除旧的IX_AppDespesaExtraOrcamentaria_TenantId_Conta索引,创建两个新的唯一索引,不会再出现重复建表的代码。
注意:不要直接手动修改新生成的迁移文件、删掉CreateTable部分就直接使用,这种方式没有修复底层快照不一致的问题,后续生成迁移还是会出现同类异常。
内容的提问来源于stack exchange,提问作者Heitor Giacomini
相关产品推荐
相关产品推荐

