「C#」EF Core的“迁移”(Migration)

news/2024/9/18 14:49:27/ 标签: c#, 开发语言, sql, .net

1、“迁移”是什么

“迁移”(Migration)我觉得可以理解为将实体类的变化 转换为对数据库修改的方案,应用迁移就是将这个修改方案应用到数据库。其次,迁移也记录了数据库的版本历史等信息。

2、添加迁移

2.1、dotnet cli tool

参考:EF Core 工具参考 (.NET CLI) - EF Core

  1. 添加迁移等后续操作用到了dotnet的命令行工具,这里记录下工具的安装(前提是已经安装了dotnet)
dotnet tool install --global dotnet-ef
  1. 更新ef工具
dotnet tool update --global dotnet-ef
  1. 确保项目中添加了Microsoft.EntityFrameworkCore.Design,可以通过VS的Nuget工具搜索添加,或者通过dotnet安装:
dotnet add package Microsoft.EntityFrameworkCore.Design
  1. 由于本文示例都是用Sqlite举例的,所以也需要添加Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

2.2、添加用于测试的实体类

分类:

public class Category
{public int CategoryId { get; set; }public string? Name { get; set; }public virtual ObservableCollectionListSource<Product> Products { get; } = new();
}

产品:

public class Product
{public int ProductId { get; set; }public string? Name { get; set; }public int CategoryId { get; set; }public virtual Category Category { get; set; } = null!;
}
  1. 添加数据库上下文DbContext
public class ProductsContext : DbContext
{//DbSet指定 要映射到数据库的实体类public DbSet<Product> Products { get; set; }public DbSet<Category> Categories { get; set; }//数据库protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)=> optionsBuilder.UseSqlite("Data Source=products.db");//创建时对表做必要的配置protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Category>().HasData(new Category { CategoryId = 1, Name = "Cheese" },new Category { CategoryId = 2, Name = "Meat" },new Category { CategoryId = 3, Name = "Fish" },new Category { CategoryId = 4, Name = "Bread" });modelBuilder.Entity<Product>().HasData(new Product { ProductId = 1, CategoryId = 1, Name = "Cheddar" },new Product { ProductId = 2, CategoryId = 1, Name = "Brie" },new Product { ProductId = 11, CategoryId = 2, Name = "Ham" },new Product { ProductId = 12, CategoryId = 2, Name = "Beef" },new Product { ProductId = 13, CategoryId = 2, Name = "Chicken" },new Product { ProductId = 21, CategoryId = 3, Name = "Salmon" },new Product { ProductId = 22, CategoryId = 3, Name = "Tuna" },new Product { ProductId = 24, CategoryId = 4, Name = "Rye" },new Product { ProductId = 25, CategoryId = 4, Name = "Wheat" }}
}

以上实体代码也可在微软官方教程中找到:Windows 窗体设计器入门 - EF Core

2.3、添加迁移

在项目所在目录下,启动终端,在终端中执行:

dotnet ef migrations add InitialCreate

InitialCreate是这次迁移的名称,类似于代码通过git提交仓库时的注释性文本,或者理解为一次行动的代号。可自定义,可以简单描述迁移的内容,或者用日期代替也可以。
首次迁移时EF Core 将在项目中创建一个名为“Migrations”的目录,并生成一些文件。
迁移文件:名称如“xxxx_MigrationName.cs”的文件以及名称带“Design”的子文件:“xxxx_MigrationName.Designer.cs”
文件2:名称如“xxxContextModelSnapshot.cs”文件。
增加迁移后,应用迁移就可以创建数据库了,关于应用,见下一节。

xxxContextModelSnapshot.cs文件

是对目前最新,当下的数据库模型,或者说实体的一个快照,主要作用是EF Core以其自身的规则生成对实体模型的描述。当有新的迁移时,新的迁移与这个模型快照进行对比,从而确定修改项以及数据库的升级方案。

xxxx_MigrationName.Designer.cs文件

是基于与快照对比后生成的本次迁移的相关代码,如果是第一次迁移,那么这个文件和模型快照文件基本一致,不同的是模型快照文件仅在生成迁移时有用,后续基本不会执行。而这个“xxx.Design.cs”文件其一方面会协助EFCore生成本次迁移,另一方面在后续的迁移引用时起到一定作用。

xxxx_MigrationName.cs文件

是本次迁移对数据库的具体修改方案。它通常包含两个方法:Up方法用于应用迁移,即将数据库从当前状态迁移到新的状态;Down方法用于回滚迁移,将数据库恢复到迁移前的状态。其代码大致如下:

public partial class InitialCreate : Migration
{/// <inheritdoc />protected override void Up(MigrationBuilder migrationBuilder){migrationBuilder.CreateTable(name: "Categories",columns: table => new{CategoryId = table.Column<int>(type: "INTEGER", nullable: false).Annotation("Sqlite:Autoincrement", true),Name = table.Column<string>(type: "TEXT", nullable: true)},constraints: table =>{table.PrimaryKey("PK_Categories", x => x.CategoryId);});migrationBuilder.CreateTable(name: "Products",columns: table => new{ProductId = table.Column<int>(type: "INTEGER", nullable: false).Annotation("Sqlite:Autoincrement", true),Name = table.Column<string>(type: "TEXT", nullable: true),CategoryId = table.Column<int>(type: "INTEGER", nullable: false)},constraints: table =>{table.PrimaryKey("PK_Products", x => x.ProductId);table.ForeignKey(name: "FK_Products_Categories_CategoryId",column: x => x.CategoryId,principalTable: "Categories",principalColumn: "CategoryId",onDelete: ReferentialAction.Cascade);});migrationBuilder.InsertData(table: "Categories",columns: new[] { "CategoryId", "Name" },values: new object[,]{{ 1, "Cheese" },{ 2, "Meat" },{ 3, "Fish" },{ 4, "Bread" }});migrationBuilder.InsertData(table: "Products",columns: new[] { "ProductId", "CategoryId", "Name" },values: new object[,]{{ 1, 1, "Cheddar" },//......{ 33, 4, "Soda" }});migrationBuilder.CreateIndex(name: "IX_Products_CategoryId",table: "Products",column: "CategoryId");}/// <inheritdoc />protected override void Down(MigrationBuilder migrationBuilder){migrationBuilder.DropTable(name: "Products");migrationBuilder.DropTable(name: "Categories");}
}

该文件可以进行修改,比如通过方法的入参 添加一些sql脚本。

migrationBuilder.Sql(
@"UPDATE CustomerSET FullName = FirstName + ' ' + LastName;
");

2.4、修改实体并添加新迁移
基于以上的例子,假设随着业务发展,要对Product类做简单修改,要增加价钱

public class Product
{public int ProductId { get; set; }public string? Name { get; set; }public int CategoryId { get; set; }public decimal Price { get; set; }//新增项public virtual Category Category { get; set; } = null!;
}

接着在终端命令行中,增加迁移

dotnet ef migrations add AddProductPrice

即创建了新的迁移,接下来只需要将迁移应用,就会将修改同步到数据库了。

3、迁移的应用

在添加迁移后,通过dotnet ef工具执行下面(二选一)的指令,即可将迁移应用到数据库。

#更新数据库
dotnet ef database update
# 数据库更新到指定的迁移(也可用于回滚)
dotnet ef database update AddNewTables

但是这样存在问题。即开发过程中我们不可能连接着生产数据库,对于终端本地数据库,我们也不能让用户去执行这样的命令。所以通过dotnet ef工具指令应用仅是和在开发过程中临时使用。正式情况还是需要通过sql脚本或内置代码的方式来应用

3.1、使用Sql脚本

这个是官方比较推荐的方式
通过dotnet ef 工具可以生成sql脚本

# 从创建到最新迁移的所有脚本
dotnet ef migrations script
# 从某一次迁移到最新迁移的脚本
dotnet ef migrations script LastMigration
# 从某一次到指定的另一次迁移(支持from新to旧生成回退脚本)
dotnet ef migrations script FromMigration ToMigration

以前面的示例,第一次生成迁移后,我们 通过上面的指令生成脚本,结果如下:

> dotnet ef migrations script                                                                                                        
Build started...                                                                                                                                             
Build succeeded.                                                                                                                                             
CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (                                                                                                         "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,                                                                           "ProductVersion" TEXT NOT NULL                                                                                                                           
);                                                                                                                                                           BEGIN TRANSACTION;                                                                                                                                           CREATE TABLE "Categories" (                                                                                                                                  "CategoryId" INTEGER NOT NULL CONSTRAINT "PK_Categories" PRIMARY KEY AUTOINCREMENT,                                                                      "Name" TEXT NULL                                                                                                                                         
);                                                                                                                                                           CREATE TABLE "Products" (                                                                                                                                    "ProductId" INTEGER NOT NULL CONSTRAINT "PK_Products" PRIMARY KEY AUTOINCREMENT,                                                                         "Name" TEXT NULL,                                                                                                                                        "CategoryId" INTEGER NOT NULL,                                                                                                                           CONSTRAINT "FK_Products_Categories_CategoryId" FOREIGN KEY ("CategoryId") REFERENCES "Categories" ("CategoryId") ON DELETE CASCADE                       
);                                                                                                                                                           INSERT INTO "Categories" ("CategoryId", "Name")                                                                                                              
VALUES (1, 'Cheese');                                                                                                                                        
SELECT changes();                                                                                                                                            INSERT INTO "Categories" ("CategoryId", "Name")                                                                                                              
VALUES (2, 'Meat');                                                                                                                                          
SELECT changes();                                                                                                                                            INSERT INTO "Categories" ("CategoryId", "Name")                                                                                                              
VALUES (3, 'Fish');                                                                                                                                          
SELECT changes();                                                                                                                                            INSERT INTO "Categories" ("CategoryId", "Name")                                                                                                              
VALUES (4, 'Bread');                                                                                                                                         
SELECT changes();  INSERT INTO "Products" ("ProductId", "CategoryId", "Name")                                                                                                   
VALUES (1, 1, 'Cheddar');                                                                                                                                    
SELECT changes();                                                                                                                                           
...
...
...                                                                                                                                                   
INSERT INTO "Products" ("ProductId", "CategoryId", "Name")                                                                                                   
VALUES (33, 4, 'Soda');                                                                                                                                      
SELECT changes(); CREATE INDEX "IX_Products_CategoryId" ON "Products" ("CategoryId");                                                                                          INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")                                                                                        
VALUES ('20240821064007_InitialCreate', '8.0.8');                                                                                                            COMMIT; 

(注:输出脚本中我删掉了部分重复的INSERT语句以减少篇幅)
从脚本看,除了创建Product和Categories两个表,还创建了__EFMigrationsHistory表并向其插入了本次迁移的名称以及EF Core相关一依赖的版本号
有了Sql脚本,我们一方面就可以通过数据库管理软件来执行并升级数据库,另一方面也可以检查并修改sql脚本以确保迁移的正确性。

3.2、通过代码的方式

当我们的项目是web程序,数据库只有一个(暂不考虑备份啊、多服务器之类的实际详情),我们是应该使用sql脚本来应用迁移的,但是如果我们的项目是一个桌面端程序,数据存储使用的是Sqlite本地Db文件,我们就需要通代码的方式进行应用迁移,当用户运行我们的程序时,程序自动的去升级数据库。

3.2.1、通过EFCore的函数方法

在代码中,我们可以使用DbContextDatabase.Migrate()方法来应用迁移。以下是一个示例:

var context = new MyDbContext();
//也可能是通过依赖注入的方式从服务中获取MyDbContext↓
//var context = serviceProvider.GetRequiredService<MyDbContext>()
context.Database.Migrate();

当然,也可以指定某个迁移,或者回退到某个迁移

var historyRepository = context.GetService<IHistoryRepository>();
var migrations = historyRepository.GetAppliedMigrations().ToList();
var targetMigration = migrations.LastOrDefault();
context.Database.Migrate(targetMigration.MigrationId);

3.2.1、通过 Sql 脚本的方式

通过Migrate()方法固然简单,单也有点不在掌控的感觉我。当然也是支持通过 SQL 脚本来内部执行的,按照3.1中的方法,先生成sql脚本,将生成的sql脚本写入代码的静态字符串、或者嵌入的资源,或者某个文件中。
通过代码获取sql语句,并执行即可。

string migrationScriptPath = "script.sql";
string scriptContent = File.ReadAllText(migrationScriptPath);
using (var command = context.Database.GetDbConnection().CreateCommand())
{command.CommandText = scriptContent;context.Database.OpenConnection();command.ExecuteNonQuery();
}

4、合并迁移

有时候,我们可能需要合并多个迁移。例如,多人个开发时不同的分支上进行了数据库架构的修改,我们可能需要将这些修改合并到一个迁移中。
这里讨论的合并并不是某个固定的方法或指令。
对于生成环境,我们只需要保留对实体类的修改,并把不同人创建的迁移都删掉,然后重新添加迁移即可。
但对于开发环境,当我拉取到别人的迁移时,我怎么处理已经应用过自己迁移的数据库呢。
可以这么考虑处理:
1、回滚到前一个统一版本,然后删除不同人新增的迁移,生成新的迁移,再重新应用。
2、先合并迁移,创建sql脚本,并修改,删掉自己已经应用了的部分,确保__EFMigrationsHistory中记录与迁移同步。


http://www.ppmy.cn/news/1516141.html

相关文章

25届网安秋招面试之后台信息泄露

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s…

GDB的基本使用(1)

我有话说 因为时间和精力原因&#xff0c;本文写的虎头蛇尾了&#xff0c;除了启动调试与程序执行以外只有少量截图演示&#xff0c;只是简单的说明。如果有需要可以联系我&#xff0c;我有时间的话会把演示补上&#xff0c;谢谢理解。 启动调试与程序执行 启动调试并传递参数…

SQL(MySQL)

SQL 用户管理HAVING和WHERE的区别 用户管理 -- 创建用户 -- 在"localhost"上创建一个名为"smiling"的新用户&#xff0c;密码是"smilingps" CREATE USER smilinglocalhost IDENTIFIED BY smilingps;-- 给用户授权 -- 在localhost上给"smil…

[数据集][目标检测]建筑工地楼层空洞检测数据集VOC+YOLO格式2588张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2588 标注数量(xml文件个数)&#xff1a;2588 标注数量(txt文件个数)&#xff1a;2588 标注…

7-6 分段函数2

计算分段函数&#xff0c;测试数据分别是-1、5、12。 输入格式: 输入一个数。 输出格式: 直接输出保留6位小数的结果&#xff0c;没有其它任何附加字符&#xff0c;没有宽度控制。 输入样例: 11输出样例: 0.999912输入样例: 7输出样例: 8.000000 #include <stdio.h…

【运维高级内容--MySQL】

目录 一、mysql安装 二、MySQL主从复制 一、mysql安装 yum install cmake gcc-c openssl-devel ncurses-devel.x86_64 rpcgen.x86_64 #安装依赖性 #在root路径下下载mysql-boost-5.7.44、libtirpc-devel-1.3.3-8.el9_4.x86_64.rpm安装包 yum install libtirpc-devel…

scrapy--图片管道-ImagesPipeline

免责声明:本文仅做演示与分享~ 目录 介绍 ImagesPipeline pipelines.py items.py zz.py settings.py 介绍 scrapy 还提供了处理图片、视频、音频等媒体文件的插件&#xff0c;如&#xff1a; - scrapy-images&#xff1a;用于下载和处理图片 - scrapy-video&#xff1…

GATK SampleList接口介绍

在 GATK 中&#xff0c;SampleList 是一个接口&#xff0c;用于表示一个样本列表。这些样本通常是在基因组分析过程中被处理的不同生物样本。SampleList 接口提供了访问这些样本的一些基本方法&#xff0c;通常用于多样本分析任务&#xff0c;比如变异检测或基因组重测序。 Sa…

Golang | Leetcode Golang题解之第354题俄罗斯套娃信封问题

题目&#xff1a; 题解&#xff1a; func maxEnvelopes(envelopes [][]int) int {n : len(envelopes)if n 0 {return 0}sort.Slice(envelopes, func(i, j int) bool {a, b : envelopes[i], envelopes[j]return a[0] < b[0] || a[0] b[0] && a[1] > b[1]})f : …

【数据结构-前缀异或】力扣1310. 子数组异或查询

有一个正整数数组 arr&#xff0c;现给你一个对应的查询数组 queries&#xff0c;其中 queries[i] [Li, Ri]。 对于每个查询 i&#xff0c;请你计算从 Li 到 Ri 的 XOR 值&#xff08;即 arr[Li] xor arr[Li1] xor … xor arr[Ri]&#xff09;作为本次查询的结果。 并返回一…

探索Facebook的区块链计划:未来社交网络的变革

随着区块链技术的迅速发展&#xff0c;社交网络领域正面临一场深刻的变革。Facebook&#xff0c;作为全球最大且最具影响力的社交平台之一&#xff0c;正在积极探索区块链技术的应用。本文将深入探讨Facebook的区块链计划&#xff0c;分析其潜在的变革性影响&#xff0c;并展望…

7-9 字母三角形

从键盘输入n&#xff0c;输出n行的如下图形 a ab abc abcd abcde ............. ................ 输入格式: 从键盘输入一个正整数n&#xff0c;输入数据保证不大于26。 输出格式: 如题所述的图形。注意输出的字母之间没有空格。 输入样例1: 5输出样例1: a ab a…

自动化巨头施耐德电气,部分业务被其供应商收购:之前还收购过霍尼韦尔

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 供应商逆袭&#xff1a;小鱼吃大鱼的商业奇迹 英国电气行业掀起一阵惊涛骇浪。斯塔福德郡的中型企业Goodfish Group竟然收购了全球巨头施耐德电气…

Spark MLlib 特征工程系列—特征转换Tokenizer和移除停用词

Spark MLlib 特征工程系列—特征转换Tokenizer和移除停用词 Tokenizer和RegexTokenizer 在Spark中,Tokenizer 和 RegexTokenizer 都是用于文本处理的工具,主要用于将字符串分割成单词(tokens),但它们的工作方式和使用场景有所不同。 1. Tokenizer 功能: Tokenizer 是最…

【Python机器学习】NLP——一个简陋的聊天机器人

目录 正则表达式 一个简答的聊天机器人 另一种方法 正则表达式就是一种FSM&#xff0c;同时它也给出了一种可能的NLP方法&#xff0c;即基于模式的方法。 正则表达式 现实生活中&#xff0c;密码锁其实就是一台简单的语言处理机。密码锁不能阅读和理解课本&#xff0c;但是…

nodejs搭建代理服务器解决跨域问题

1.安装express、http-proxy-middleware npm install express http-proxy-middleware2.根据情况额外再安装一个nodemon&#xff0c;可以在检测到文件变化时自动重启应用程序,省去了手动重启的麻烦 npm install nodemon搭建代理服务器 node index.js const express require(e…

大数据系统测试——大数据系统解析(上)

各位好&#xff0c;我是 道普云 欢迎关注我的主页 希望这篇文章对想提高软件测试水平的你有所帮助。 在本文中我们一起来看一下大数据系统每一个层次需要解决的技术问题和对应的一些技术需求。以此来作为学习大数据系统测试的基础。 数据收集层主要是进行数据源的分布式、…

图了个图 - 目前最满意的AI修图软件

图了个图是一款完全免费无广告的AI修图软件&#xff0c;系统占用极低&#xff0c;可以通过AI处理图片。具体功能请查看截图&#xff0c;功能丰富多样。登录后即可享受永久会员&#xff0c;所有功能全开放。目前只有安卓版&#xff0c;后续还会继续更新更多功能。 链接&#xf…

等保测评中的安全测试方法

等保测评&#xff0c;即信息安全等级保护测评&#xff0c;是我国网络安全领域的重要评估机制&#xff0c;用于验证网络系统或应用是否满足相应的安全保护等级要求。在等保测评中&#xff0c;安全测试方法扮演着至关重要的角色。本文将详细介绍等保测评中常用的安全测试方法及其…

用阿里云“无影”搭建《黑神话:悟空》电脑环境

目录 《黑神话&#xff1a;悟空》 阿里云无影试用版概述 阿里云无影云电脑试用版情况 具体详细过程&#xff08;搭建环境&#xff09; 《黑神话&#xff1a;悟空》 《黑神话&#xff1a;悟空》作为一款高品质的国产游戏&#xff0c;对硬件配置有一定的要求。根据公开发布的…