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

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.mzph.cn/diannao/52529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Java Maven项目中使用JUnit 5进行测试

如何在Java Maven项目中使用JUnit 5进行测试 1. 简介 JUnit 5概述 JUnit是Java编程语言中最流行的测试框架之一。JUnit 5是JUnit的最新版本&#xff0c;它引入了许多新特性和改进&#xff0c;使得编写和运行测试更加灵活和强大。 为什么选择JUnit 5 JUnit 5不仅提供了更强…

Furion+SqlSugar+Swagger企业级后端工程师 - 学习路线总目录

一、Furion框架介绍 Furion 是一个基于 .NET 5 平台开发的框架&#xff08;Furion v5 版本采用 C# 12 和 .NET 8 进行开发。&#xff09;&#xff0c;致力于使 .NET 开发过程更简单、通用和流行。该框架的名字“Furion”源自中文“先知”&#xff0c;意味着它旨在领先和预见技…

设计模式反模式:UML图示常见误用案例分析

第一章 引言 1.1 设计模式与反模式概述 在软件开发领域&#xff0c;设计模式与反模式是两种截然不同的概念&#xff0c;它们在软件设计过程中起着至关重要的作用。设计模式是经过验证的最佳实践&#xff0c;用于解决在特定上下文中经常出现的问题&#xff0c;从而提高软件的可…

《黑神话·悟空》是用什么编程语言开发的?

最近火爆全球的国产 3A 大作《黑神话悟空》&#xff0c;你玩了吗&#xff1f;没玩没关系&#xff0c;有人就是对游戏不感冒&#xff0c;我找了个宣发片&#xff0c;一起感受下3A大作的视觉冲击&#xff0c;而且还是我们从小听到大&#xff0c;那猴子&#x1f412;的故事。 ‌‌…

【Python进阶】面向对象编程:用Python实现类与对象

1、面向对象编程概论 1.1 面向对象编程起源与发展 面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;并非一夜之间凭空诞生的概念&#xff0c;它的历史可以追溯到20世纪60年代末期&#xff0c;当时Simula 67被认为是首个支持面向对象编程的编程语言。这…

【Linux】自动化构建工具makefile

目录 背景 makefile简单编写 .PHONY makefile中常用选项 makefile的自动推导 背景 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 ​ ◉ 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…

Scrapy 项目部署Scrapyd

什么是Scrapyd Scrapyd 是一个用来管理和运行 Scrapy 爬虫的服务。它允许用户将 Scrapy 项目部署到服务器上&#xff0c;然后通过一个简单的 API 来启动、停止和监控爬虫的运行。Scrapyd 可以帮助简化爬虫的部署过程&#xff0c;使得用户不必手动在服务器上运行爬虫&#xff0c…

【测试】JMeter从入门到进阶

本文参考 Jmeter自动化测试工具从入门到进阶6小时搞定&#xff0c;适合手工测试同学学习_哔哩哔哩_bilibili JMeter介绍 JMeter 是 Apache 组织使用 Java 开发的一款测试工具&#xff1a; 1、可以用于对服务器、网络或对象模拟巨大的负载 2、通过创建带有断言的脚本来验证程序…

9个最流行的文本转语音引擎【TTS 2024】

在快速发展的技术世界中&#xff0c;文本转语音 (TTS) 引擎正在取得显著进步。从增强各种应用程序中的用户体验到创建逼真且引起情感共鸣的语音输出&#xff0c;TTS 引擎正变得不可或缺。在这里&#xff0c;我们介绍了 2024 年为行业树立新标准的九款最佳 TTS 引擎。 NSDT工具推…

应用层协议(上)Http(URL、Cookie、Session)内含逻辑图解通俗易懂!

绪论​ “少年没有乌托邦 心向远方自明朗”&#xff0c;本章是应用层常用且重要的协议htttp&#xff0c;没看过应用层建议一定先看那一篇后再看本章才能更好的去从上到下的理解应用层。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 1.Http协…

Mac移动硬盘选什么格式最好 Mac怎么用ntfs移动硬盘

在使用Mac电脑的过程中&#xff0c;很多用户可能有需要扩展存储空间的需求。选择合适的移动硬盘格式对于数据传输的效率和兼容性至关重要。本文将详细介绍Mac移动硬盘选什么格式好&#xff0c;以及Mac怎么用ntfs移动硬盘&#xff0c;帮助用户优化Mac的使用体验。 一、Mac移动硬…

悬浮翻译工具有哪些?工作学习必备的5款悬浮翻译工具

当我们身处异国他乡&#xff0c;或是工作中遇到多语种交流的需求时&#xff0c;语言障碍往往会成为一道难以逾越的高墙。 不过&#xff0c;在这个充满创新的时代里&#xff0c;技术已经为我们准备好了答案——屏幕翻译器app。它们不仅能够即时翻译屏幕上的文字&#xff0c;还能…

电脑回收站清空了怎么恢复?

在日常使用电脑的过程中&#xff0c;不小心清空回收站导致重要文件丢失的情况时有发生。面对这种情况&#xff0c;我们不必过于慌张&#xff0c;因为有多种方法可以尝试恢复被清空的文件。本文将为您详细介绍几种有效的恢复方法&#xff0c;帮助您找回宝贵的文件。 方法一&…

芯片后端之 PT 使用 report_timing 产生报告 之 -nets 选项

今天,我们再学习一点点 后仿真相关技能。 那就是,了解 report_timing 中的 -nets 选项 。 如果我们仅仅使用如下命令,执行后会发现: pt_shell> report_timing -from FF1/CK -to FF2/d -delay_type max 我们使用命令 report_timing 报出的如上路径延时信息,仅仅显示…

Maven的一些相关知识【重修】《包括私服搭建!》

mvnrepository.com Maven 下载jar包的位置&#xff01; 【该部分有教程】 这是什么nb代码投稿视频-这是什么nb代码视频分享-哔哩哔哩视频 MAVEN 的私服搭建&#xff1a; https://zhuanlan.zhihu.com/p/520107316 2、maven私服搭建及应用&#xff08;下&#xff09;_哔哩…

高级java每日一道面试题-2024年8月25日-前端篇(Vue篇)-v-show和v-if有什么区别?

如果有遗漏,评论区告诉我进行补充 面试官: v-show和v-if有什么区别? 我回答: 在Vue.js中&#xff0c;v-if和v-show都是用于根据条件控制元素显示或隐藏的指令&#xff0c;但它们之间存在几个关键的区别。以下是这两个指令的主要区别&#xff1a; 1. 渲染方式 v-if&#x…

R7RS标准之重要特性及用法实例(三十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

微信小程序如何存储值

微信小程序存储值的方法主要包括本地存储和云存储两种方式。以下是这两种方式的详细介绍&#xff1a; 一、本地存储 本地存储是在用户的设备上保存数据的技术&#xff0c;使得数据在小程序关闭后仍能保留。微信小程序提供了多种API来实现本地存储功能。 1. 缓存数据 方式&a…

公考面试笔记_社会现象类1

目录 1.线上审批2.社交恐惧3.食品安全4.安全生产5.残疾人6.明星睡觉直播7.不文明现象8.胡言乱语的专家9. 缝缝补补又三年 老夏说面试自学笔记整理&#xff0c;自学用~ 1.线上审批 政府鼓励推广线上审批形式&#xff0c;利用线上及自助柜员机等帮助群众办理审批。但有单位强制…

frameworks 之InputReader

frameworks 之InputReader InputManagerService 初始化InputManagerService 启动InputReader 事件的读取设备节点注册和监听设备输入事件的读取 InputReader 事件的处理设备的添加和删除处理触摸事件的处理数据的加工和分发 android 输入事件 主要分 2个流程 事件读取 和 事件…