VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#EF Core迁移——数据库版本控制

第一部分:C#基础入门
7.EF Core迁移——数据库版本控制
实例介绍
你有没有遇到过这样的场景?
团队协作时,张三在本地加了个Product表的Discount列,李四拉代码后运行程序直接报错——数据库结构不一致;
部署到生产环境时,手动写SQL脚本更新数据库,结果漏了某个索引,导致线上功能异常;
刚上线的新功能发现数据库表设计错了,想回滚却只能手动删表改字段,风险极高。
这些问题的根源,都是数据库版本没有和代码同步。EF Core迁移vb.net教程C#教程python教程SQL教程access 2010教程就是解决这个痛点的“神器”——它把数据库结构的每一次变更都变成代码里的“迁移文件”,像Git管理代码版本一样管理数据库,团队协作同步、部署平滑升级、错误快速回滚,全都搞定。
需求分析
本节要解决的核心问题:
1.数据库版本控制:跟踪每一次结构变更(加表、改列、删索引等);
2.团队协作同步:成员间一键同步数据库结构,避免“本地能跑线上崩”;
3.部署平滑升级:生产环境无需手动写SQL,用迁移自动更新;
4.错误回滚:发现问题时,一键恢复到之前的数据库版本;
5.数据迁移:结构变更时(比如改列类型),安全处理现有数据。
目标:掌握EF Core迁移的全流程,从创建到部署,从基础到高级,避开90%的坑点。
代码实现
前置条件:.NET 6+、EF Core 7+;需安装EF Core工具(dotnet tool install --global dotnet-ef);引用Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer。
准备工作:创建实体与DbContext
先定义两个实体(Category和Product),以及对应的DbContext:
csharp

	// 分类实体
	public class Category
	{
	public int Id { get; set; }
	public string Name { get; set; } = null!; // 非空标注
	public ICollection<Product> Products { get; set; } = new List<Product>(); // 一对多关联
	}
	
	// 商品实体
	public class Product
	{
	public int Id { get; set; }
	public string Name { get; set; } = null!;
	public decimal Price { get; set; }
	public int CategoryId { get; set; } // 外键
	public Category Category { get; set; } = null!; // 导航属性
	}
	
	// DbContext配置
	public class ShopDbContext : DbContext
	{
	public DbSet<Category> Categories { get; set; } = null!;
	public DbSet<Product> Products { get; set; } = null!;
	
	protected override void OnConfiguring(DbContextOptionsBuilder options)
	{
	// 替换为你的数据库连接字符串
	options.UseSqlServer("Server=.;Database=ShopDB;Trusted_Connection=True;");
	}
	}

场景1:迁移基本流程(创建→应用→验证)
错误写法1:直接运行程序,EF Core自动创建数据库(无迁移记录)
csharp

	// ❌错误:没有迁移时,EF Core会自动创建数据库,但后续无法跟踪变更
	public static async Task Main(string[] args)
	{
	using var db = new ShopDbContext();
	await db.Database.EnsureCreatedAsync(); // 自动创建数据库,但无迁移记录
	}

问题:后续修改实体(比如给Product加Discount列),EnsureCreatedAsync不会更新数据库,只能手动改。
错误写法2:手动修改数据库后,再生成迁移(导致冲突)
sql

	-- ❌错误:手动在数据库加列,和EF Core模型不一致
	ALTER TABLE Products ADD Discount DECIMAL(18,2) DEFAULT 0;

问题:生成迁移时,EF Core会认为Discount列不存在(因为模型里还没加),导致迁移脚本重复添加列,运行时报错。
正确流程:用迁移管理所有变更
步骤1:生成第一次迁移
打开命令行,执行:
bash

	# 命令行方式(推荐)
	dotnet ef migrations add InitialCreate --context ShopDbContext
	
	# Visual Studio Package Manager Console方式
	Add-Migration InitialCreate -Context ShopDbContext

执行结果:项目里会生成Migrations文件夹,包含三个文件:
InitialCreate.cs:迁移逻辑(Up=升级,Down=回滚);
InitialCreate.Designer.cs:迁移元数据(EF Core内部用);
ShopDbContextModelSnapshot.cs:当前模型的“快照”(用于对比后续变更)。
步骤2:应用迁移到数据库
执行命令:
bash

	# 命令行
	dotnet ef database update --context ShopDbContext
	
	# PMC
	Update-Database -Context ShopDbContext

执行结果:
数据库ShopDB被创建;
生成__EFMigrationsHistory表(记录已应用的迁移);
生成Categories和Products表(对应实体)。
步骤3:验证迁移
打开SQL Server Management Studio(SSMS),查看ShopDB:
有Categories、Products两个表;
有__EFMigrationsHistory表,里面有一条记录:InitialCreate。
场景2:修改实体后生成新迁移
给Product加Discount列,修改实体:
csharp

	public class Product
	{
	public int Id { get; set; }
	public string Name { get; set; } = null!;
	public decimal Price { get; set; }
	public decimal Discount { get; set; } = 0; // 新增列
	public int CategoryId { get; set; }
	public Category Category { get; set; } = null!;
	}

生成新迁移:
bash
dotnet ef migrations add AddProductDiscount --context ShopDbContext
迁移文件AddProductDiscount.cs内容:
csharp

	public partial class AddProductDiscount : Migration
	{
	protected override void Up(MigrationBuilder migrationBuilder)
	{
	// 升级操作:给Products表加Discount列
	migrationBuilder.AddColumn<decimal>(
	name: "Discount",
	table: "Products",
	type: "decimal(18,2)",
	nullable: false,
	defaultValue: 0m); // 默认值0
	}
	
	protected override void Down(MigrationBuilder migrationBuilder)
	{
	// 回滚操作:删除Discount列
	migrationBuilder.DropColumn(
	name: "Discount",
	table: "Products");
	}
	}

应用迁移到数据库:
bash
dotnet ef database update AddProductDiscount --context ShopDbContext
验证:Products表会新增Discount列,__EFMigrationsHistory表新增AddProductDiscount记录。
场景3:回滚迁移(恢复到之前的版本)
如果发现Discount列设计错了(比如应该是int不是decimal),回滚到InitialCreate版本:
bash

	# 回滚到InitialCreate(指定迁移名称)
	dotnet ef database update InitialCreate --context ShopDbContext
	
	# 或者回滚到上一个版本(不用记名称)
	dotnet ef database update --context ShopDbContext --verbose

执行结果:
Products表的Discount列被删除;
__EFMigrationsHistory表中AddProductDiscount记录被移除;
代码里的AddProductDiscount迁移文件还在,需要手动删除(或保留,后续修改后重新生成)。
场景4:生成SQL脚本(用于生产部署)
生产环境可能不允许直接运行dotnet ef命令,这时可以生成SQL脚本,手动执行:
bash

	# 生成从InitialCreate到AddProductDiscount的脚本(指定起始和结束迁移)
	dotnet ef migrations script InitialCreate AddProductDiscount --context ShopDbContext --output MigrateToAddDiscount.sql --no-build
	
	# 生成所有未应用的迁移脚本(生产环境常用)
	dotnet ef migrations script --context ShopDbContext --output LatestMigrations.sql --no-build

脚本内容示例:
sql

	BEGIN TRANSACTION;
	GO
	
	CREATE TABLE [__EFMigrationsHistory] (
	[MigrationId] nvarchar(150) NOT NULL,
	[ProductVersion] nvarchar(32) NOT NULL,
	CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
	);
	GO
	
	-- 省略InitialCreate的脚本...
	
	ALTER TABLE [Products] ADD [Discount] decimal(18,2) NOT NULL DEFAULT 0.0;
	GO
	
	INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
	VALUES (N'20240520123456_AddProductDiscount', N'7.0.10');
	GO
	
	COMMIT;

场景5:高级用法——数据迁移(改列类型)
假设要把Product的Price列从decimal(18,2)改成decimal(18,4)(更精确),直接改模型会导致数据丢失吗?不会,用迁移安全处理:
步骤1:生成迁移(但不要直接应用)
先给Product的Price改类型:
csharp

	public class Product
	{
	// ...其他属性
	public decimal Price { get; set; } = 0m; // EF Core默认生成decimal(18,2),需要自定义
	}
	
	// 在ShopDbContext的OnModelCreating里配置列类型
	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
	modelBuilder.Entity<Product>()
	.Property(p => p.Price)
	.HasColumnType("decimal(18,4)"); // 改成18,4
	}

生成迁移:
bash
dotnet ef migrations add ChangePricePrecision --context ShopDbContext
步骤2:修改迁移文件,添加数据迁移逻辑
默认生成的ChangePricePrecision.cs会直接删除旧列再添加新列(导致数据丢失),需要手动修改:
csharp

	public partial class ChangePricePrecision : Migration
	{
	protected override void Up(MigrationBuilder migrationBuilder)
	{
	// 1. 添加临时列(保存旧数据)
	migrationBuilder.AddColumn<decimal>(
	name: "TempPrice",
	table: "Products",
	type: "decimal(18,4)",
	nullable: false,
	defaultValue: 0m);
	
	// 2. 把旧列数据迁移到临时列
	migrationBuilder.Sql(@"
	UPDATE Products 
	SET TempPrice = CAST(Price AS DECIMAL(18,4))
	");
	
	// 3. 删除旧列
	migrationBuilder.DropColumn(
	name: "Price",
	table: "Products");
	
	// 4. 重命名临时列为Price
	migrationBuilder.RenameColumn(
	name: "TempPrice",
	table: "Products",
	newName: "Price");
	}
	
	protected override void Down(MigrationBuilder migrationBuilder)
	{
	// 回滚逻辑:反向操作
	migrationBuilder.AddColumn<decimal>(
	name: "TempPrice",
	table: "Products",
	type: "decimal(18,2)",
	nullable: false,
	defaultValue: 0m);
	
	migrationBuilder.Sql(@"
	UPDATE Products 
	SET TempPrice = CAST(Price AS DECIMAL(18,2))
	");
	
	migrationBuilder.DropColumn(
	name: "Price",
	table: "Products");
	
	migrationBuilder.RenameColumn(
	name: "TempPrice",
	table: "Products",
	newName: "Price");
	}
	}

步骤3:应用迁移
bash
dotnet ef database update ChangePricePrecision --context ShopDbContext
验证:Price列类型变成decimal(18,4),原有数据保留(比如19.99变成19.9900)。
逐行讲解
场景2:迁移文件核心逻辑
以InitialCreate.cs为例:
csharp

	public partial class InitialCreate : Migration
	{
	protected override void Up(MigrationBuilder migrationBuilder)
	{
	// 创建Categories表
	migrationBuilder.CreateTable(
	name: "Categories",
	columns: table => new
	{
	Id = table.Column<int>(type: "int", nullable: false)
	.Annotation("SqlServer:Identity", "1, 1"), // SQL Server自增标识
	Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
	},
	constraints: table =>
	{
	table.PrimaryKey("PK_Categories", x => x.Id); // 主键
	});
	
	// 创建Products表(含外键关联Categories)
	migrationBuilder.CreateTable(
	name: "Products",
	columns: table => new
	{
	Id = table.Column<int>(type: "int", nullable: false)
	.Annotation("SqlServer:Identity", "1, 1"),
	Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
	Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
	CategoryId = table.Column<int>(type: "int", nullable: false)
	},
	constraints: table =>
	{
	table.PrimaryKey("PK_Products", x => x.Id);
	// 外键约束:Products.CategoryId → Categories.Id
	table.ForeignKey(
	name: "FK_Products_Categories_CategoryId",
	column: x => x.CategoryId,
	principalTable: "Categories",
	principalColumn: "Id",
	onDelete: ReferentialAction.Cascade); // 删除分类时,自动删除关联商品
	});
	}
	
	protected override void Down(MigrationBuilder migrationBuilder)
	{
	// 回滚时,先删外键关联的表(Products),再删主表(Categories)
	migrationBuilder.DropTable(
	name: "Products");
	
	migrationBuilder.DropTable(
	name: "Categories");
	}
	}

关键说明:
MigrationBuilder:EF Core提供的工具类,用于生成SQL语句;
Up方法:升级数据库时执行的逻辑(创建表、加列等);
Down方法:回滚时执行的逻辑(必须是Up的逆操作,比如删表对应创建表);
Annotation:用于指定数据库特定的特性(比如SQL Server的自增、MySQL的 AUTO_INCREMENT);
ForeignKey:定义外键关联,onDelete: ReferentialAction.Cascade表示级联删除。
场景5:数据迁移逻辑
修改ChangePricePrecision.cs时,为什么要加临时列?
直接删除旧列会丢失数据;
用临时列保存旧数据,迁移后再重命名,确保数据不丢失;
migrationBuilder.Sql:执行自定义SQL语句(EF Core无法自动生成的逻辑,比如数据转换)。
基础知识拓展

  1. 迁移的工作原理
    EF Core迁移的核心是模型快照和迁移历史表:
    模型快照:ShopDbContextModelSnapshot.cs文件,记录当前EF Core模型的结构(表、列、关联等);
    迁移历史表:数据库中的__EFMigrationsHistory表,记录已应用的迁移名称和EF Core版本;
    生成迁移:对比当前模型和快照,找出差异,生成Up/Down逻辑;
    应用迁移:执行未应用的迁移的Up方法,更新数据库和历史表;
    回滚迁移:执行已应用的迁移的Down方法,恢复数据库和历史表。
  2. 迁移 vs 手动SQL脚本
    对比维度 EF Core迁移 手动SQL脚本
    版本控制 和代码绑定,Git管理 独立文件,易丢失/版本不一致
    团队协作 一键同步,无冲突 需手动分享,易重复/遗漏
    回滚 一键回滚,安全可靠 需手动写回滚脚本,易出错
    跨数据库 自动适配(SQL Server/MySQL等) 需针对不同数据库写不同脚本
    复杂逻辑 支持自定义SQL,灵活 完全灵活,但需手动维护
  3. 最佳实践
    禁止手动修改数据库:所有变更必须通过迁移,否则会导致模型和数据库不一致;
    每个迁移对应一个功能:不要一次生成多个功能的迁移(比如同时加表和改列),方便回滚;
    测试迁移:在开发环境测试所有迁移(包括Up和Down),确保无错误;
    生产环境用脚本部署:生成SQL脚本后,先在测试环境验证,再手动执行到生产;
    备份数据库:应用迁移前,务必备份生产数据库(即使迁移有Down方法,也不能完全保证数据安全);
    命名规范:迁移名称要清晰(比如AddProductDiscount、ChangePricePrecision),不要用Migration1这种无意义的名称。
  4. 常见坑点与解决
    迁移冲突:多人同时修改同一实体,生成迁移时会冲突。解决:先pull最新代码,合并模型,再重新生成迁移;
    迁移失败:应用迁移时出错(比如SQL语法错误),先回滚到上一个版本,修改迁移文件,再重新应用;
    快照文件冲突:Git合并时,ShopDbContextModelSnapshot.cs容易冲突。解决:删除冲突的快照文件,重新生成迁移(dotnet ef migrations add MergeSnapshot --context ShopDbContext);
    数据丢失:修改列类型或删除列时,一定要手动添加数据[vb.net教程](https://www.xin3721.com/eschool/vbnetxin3721/)[C#教程](https://www.xin3721.com/eschool/CSharpxin3721/)[python教程](https://www.xin3721.com/eschool/pythonxin3721/)[SQL教程](https://www.xin3721.com/eschool/SQLxin3721/)[access 2010教程](https://www.xin3721.com/eschool/Accessxin3721/)
     
    迁移逻辑(比如临时列),不要用EF Core默认生成的逻辑。
    总结
    EF Core迁移的核心价值,是让数据库结构成为代码的一部分,彻底解决版本不一致的问题。掌握它的关键:
    1.核心流程:生成迁移→应用迁移→验证→回滚;
    2.关键命令:migrations add(生成)、database update(应用/回滚)、migrations script(生成脚本);
    3.高级技巧:自定义迁移逻辑(用Sql方法)、数据迁移(临时列)、生产环境用脚本部署;
    4.避坑指南:禁止手动改数据库、测试所有迁移、备份生产数据。
    学会EF Core迁移,你就能告别“数据库地狱”,让团队协作和部署变得轻松愉快!

本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49455.html


相关教程