【EF Core】为 DatabaseFacade 扩展“创建”与“删除”数据表功能
对于玩 EF 的大伙伴来说,对 DatabaseFacade 类的 EnsureCreated 和 EnsureDeleted 方法应该很熟悉。这对方法可在运行阶段创建或删除数据库。创建数据库时,会连同数据表一起创建;至于说删除数据库时嘛……库都没了,哪还有表呢。
不过,有些时候,不,不是有些时候,很多时候我们其实只想删除数据表。比如要初始化应用程序;或者数据库已存在的情况下,咱们只考虑判断数据表是否存在,不存在的话就创建表。
最简单的方法是直接发送 SQL 语句——如 DROP TABLE、DROP DATABASE 等。这个不在本文的讨论范围内。老周这次讨论的是运用 EF Core 自身的功能去实现。
咱们来热热身——先学点理论知识,有了这些知识,后面实战起来会容易很多。老掉牙的话:EF Core 也是基于服务容器。即支持依赖注入,这个东西好用着呢,也方便定制和扩展。微软在 .NET 上基本贯彻这个路线了。
咱们都知道,为了方便我们用面向对象的方式操作数据库,EF Core 内部实现了将相关操作以及 LINQ 转译为 SQL 的功能。尽管不能够完全覆盖 SQL 所有功能,但常用的一个不落。
下面介绍几个重量级角色:
NO.1:IMigrationsSqlGenerator 接口。
这个接口的功能很关键,看名字就知道,用来生成 SQL 语句的。这个接口只有一个 Generate 方法,它的签名比较长:
public IReadOnlyList<MigrationCommand> Generate(IReadOnlyList<MigrationOperation> operations, IModel? model = default, MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default);
第三个参数一般保留默认,第一个参数是一系列 MigrationOperation 对象;第二个参数是模型对象,这个可以从 DbContext.Model 属性返回。MigrationOperation 是一个抽象类,表示一个数据库迁移操作(对的,dotnet ef migration 命令调用的正是迁移 API 来生成 SQL,再将其发送到数据库执行)。它的每一个派生类都代表一种操作。比如,DropColumnOperation 表示从数据表中删除一列,AddUniqueConstraintOperation 用于向数据表添加唯一约束。
注意,这些 Operation 类型其实是“参数封装”器,各个属性用于收集此操作所需要的信息,各个数据库提供者都应该实现具体的操作。比如,在 SQL Server 提供者中,用于创建数据库的 SqlServerCreateDatabaseOperation 类,Name 属性用于设置数据库的名字;FileName 属性设置数据库文件的路径,比如连接字符串中使用了 AttachDbFile 字段。
现在说回 IMigrationsSqlGenerator 接口,它有个基础实现类—— MigrationsSqlGenerator。数据库提供者通常不必直接实现接口,而是从该类派生。例如官方默认实现的 SqliteMigrationsSqlGenerator 类和 SqlServerMigrationsSqlGenerator 类。Generate 方法返回一个 MigrationCommand 列表,这个列表传递给 IMigrationCommandExecutor 接口的 ExecuteNonQuery 或 ExecuteNonQueryAsync 方法就会执行 SQL 命令了。
NO.2:IMigrationCommandExecutor 接口
这个流程很明确,XXXSqlGenerator 负责生成 SQL,XXXCommandExecutor 负责执行命令。这个逻辑相信大伙伴们都能理解。这还不够,咱们还不知道怎么连接数据库呢,执行 SQL 前肯定得连接数据库的哟。于是,有请第三位。
NO.3:IRelationalConnection 接口
这个也不必过多介绍,就是用来连接数据库的。默认的基础类是 RelationalConnection。不用意外,这货一定是抽象类的。毕竟,不同数据库的连接方式是不同的,所以,这个得让数据库提供者们自己去实现。比如面向 SQL Server 的 SqlServerConnection 类,面向 SQLite 的 SqliteRelationalConnection 类。
当然了,IRelationalConnection 对象只是套个壳添加必要的逻辑,真正完成连接数据库任务的是 DbConnection 类(通过 DbConnection 属性引用)。如连接 Sqlite 数据库的 SqliteConnection 类。
NO.4:IDatabaseCreator 接口
这个接口从名字上也能猜到,它是用来创建或删除数据库的。这个接口包含了咱们非常熟悉的 EnsureCreated 和 EnsureDeleted 方法。通常,咱们在用的时候不会从服务容器中获取此接口,而是它的派生接口—— IRelationalDatabaseCreator。重点来了,这。
再明显不过了,HasTables 返回 bool 值,表示数据表是否已存在;CreateTables 就是创建数据表。不过,DatabaseFacade 类没有这些方法,它只能创建、删除数据库。所以,咱们要做的,就是给它加扩展方法,把创建、删除表的方法封装出来。
于是,咱们在实战部分,要分两个阶段。
=====================================================================================
理论课上完了,下面咱们动手实践。由于 IRelationalDatabaseCreator 已经实现了判断数据表存在性和创建数据表的功能,那咱们直接用它。先要定义一个静态类,比如,叫 MyDatabaseFacadeExtension。
public static class MyDatabaseFacadeExtension
{
……
}
为了尽量少写重复代码,可以先搞个私有的扩展方法,用来获取 IRelationalDatabaseCreator。
private static IRelationalDatabaseCreator GetDBCreator(this DatabaseFacade db)
{
return db.GetService<IRelationalDatabaseCreator>();
}
这个很简单,DatabaseFacade 对象就有一个 GetService 方法,可直接从服务容器中取对象。
接着实现数据表的存在性判断—— HasTables。
/// <summary>
/// 判断表是否存在
/// </summary>
public static bool HasTables(this DatabaseFacade db)
{
IRelationalDatabaseCreator creator = db.GetDBCreator();
return creator.HasTables();
}
public static Task<bool> HasTablesAsync(this DatabaseFacade db)
{
IRelationalDatabaseCreator creator = db.GetDBCreator();
return creator.HasTablesAsync();
}
随后就是创建表的封装,也很简单。
/// <summary>
/// 创建表
/// </summary>
public static void CreateTables(this DatabaseFacade db)
{
IRelationalDatabaseCreator creator = db.GetDBCreator();
creator.CreateTables();
}
public static async Task CreateTablesAsync(this DatabaseFacade db)
{
IRelationalDatabaseCreator creator = db.GetDBCreator();
await creator.CreateTablesAsync();
}
上面的都好弄,最难的来了!IRelationalDatabaseCreator 只实现了创建表,可没有删除表啊。所以,只好自己动手了。
回想一下前文的理论热身,咱们是不是需要三个服务接口?
1、IRelationalConnection:负责连接数据库;
2、IMigrationsSqlGenerator:负责生成 SQL 命令;
3、IMigrationCommandExecutor:负责执行命令。
由于在调用服务方法时咱们需要 IModel,而它一般可从 DbContext 对象的 Model 属性获取。所以咱们还要想办法从 DatabaseFacade 对象中获取当前 DbContext 对象的引用(其实是从 DbContext 派生的类实例)。仔细观察,可发现 DatabaseFacade 类显示实现了 IDatabaseFacadeDependenciesAccessor 接口,而这个接口有个 Context 属性,正好能获取到 DbContext 实例。
好了,现在,所有难题都解决了,可以开干了。
/// <summary>
/// 删除表
/// </summary>
public static void RemoveTables(this DatabaseFacade db)
{
// 获取服务
IRelationalConnection conn = db.GetService<IRelationalConnection>();
IMigrationsSqlGenerator generator = db.GetService<IMigrationsSqlGenerator>();
IMigrationCommandExecutor executor = db.GetService<IMigrationCommandExecutor>
();
// 获取 DbContext 实例
DbContext context = ((IDatabaseFacadeDependenciesAccessor)db).Context;
// 获取 Model 实例
IModel model = context.Model;
List<MigrationOperation> operations = new();
// 看看要删除哪些表
foreach (var entity in model.GetEntityTypes())
{
DropTableOperation drpopr = new()
{
// 被删除表的架构名
Schema = entity.GetSchema(),
// 被删除表的表名
Name = entity.GetTableName()!
};
operations.Add(drpopr);
}
// 构建命令
var commands = generator.Generate(operations, model);
// 执行命令
executor.ExecuteNonQuery(commands, conn);
}
删除数据表,对应的迁移操作是 DropTable,所以实例化一个 DropTableOperation 对象,并设置要删除的表名(可能还有架构名)。接着生成 SQL 命令,最后执行它。完事。
为了兼容,还可以实现异步版本。
public static async Task RemoveTablesAsync(this DatabaseFacade db)
{
// 获取DbContext实例
DbContext context = ((IDatabaseFacadeDependenciesAccessor)db).Context;
// 获取服务
IMigrationCommandExecutor executor = context.
GetService<IMigrationCommandExecutor>();
IMigrationsSqlGenerator generator = context.
GetService<IMigrationsSqlGenerator>();
IRelationalConnection conn = context.GetService<IRelationalConnection>();
// 构建operation列表
List<MigrationOperation> operations = new();
// 删除所有表
foreach (var ent in context.Model.GetEntityTypes())
{
operations.Add(new DropTableOperation()
{
Schema = ent.GetSchema(),
Name = ent.GetTableName()!
});
}
// 生成命令
var cmds = generator.Generate(operations, context.Model);
// 执行命令
await executor.ExecuteNonQueryAsync(cmds, conn);
}
这里注意,Model 可能不只一个实体类,即不只一个表,所以要 foreach 逐个访问 IModel.GetEntityTypes 方法返回的实体类型集合。把实体对应的表都删除。
可以测试一下。假设用 SQL Server ,事先建一个数据库,里面没有表。
public class Student
{
public int ID { get; set; }
public string Name { get; set; } = string.Empty;
public string? Major { get; set; }
public int Age { get; set; }
public string? Email { get; set; }
} public class MyDbContext : DbContext
{ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("server=(localdb)\\MSSQLLOCALDB; database=Demo");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>(et =>
{
et.HasKey(x => x.ID).HasName("PK_Student");
et.Property(x => x.ID).HasColumnName("sid")
.UseIdentityColumn(100, 1);
}); }
}
模型配置时,让主键例的名字为 sid。
运行程序时,检查有没有数据表,没有就创建。
var dc = new MyDbContext(); if (!dc.Database.HasTables())
{
dc.Database.CreateTables();
}
若需要删除数据表,可以这样写:
dc.Database.RemoveTables();
怎么样,这样封装后是不是就和官方 API 那样方便?
【EF Core】为 DatabaseFacade 扩展“创建”与“删除”数据表功能的更多相关文章
- SQL Server 创建 修改 删除数据表
1. 图形界面方式操作数据表 (1)创建和修改数据表 列名中如果有两个以上单词时,最好用下划线连接,否则可能会给将来的查询维护带来不便.我们公司美国佬做的数据库就很烦,所有列名都有空格,一旦忘记用方括 ...
- MySQL 创建和删除数据表
创建MySQL数据表需要以下信息: 表名 表字段名 定义每个表字段 语法 以下为创建MySQL数据表的SQL通用语法: CREATE TABLE table_name (column_name col ...
- sql server2008中怎样用sql语句创建数据库和数据表
这是简单用代码实现创建数据库和数据表的sql语句,如下: --调用系统数据库-- use master go /***防止你要创建的数据库同名,先把它删除掉****/ if Exists(select ...
- WordPress插件制作教程(五): 创建新的数据表
上一篇讲解了怎样将数据保存到数据库,今天为大家讲解创建新的数据表,也就是说当我们激活插件的时候,会在该数据库下面创建一个新的数据表出来.原理很简单,激活插件的时候运行创建数据库的代码.看下面代码: & ...
- Mysql 中如何创建数据库和数据表
这里的数据库为:user 数据表为 aaa mysql –uroot –p 进入mysql create database user; 创建数据 ...
- 【spring boot】spring boot 2.0 项目中使用mysql驱动启动创建的mysql数据表,引擎是MyISAM,如何修改启动时创建数据表引擎为【spring boot 2.0】
默认创建数据表使用的引擎是MyISAM 2018-05-14 14:16:37.283 INFO 7328 --- [ restartedMain] org.hibernate.dialect.Dia ...
- 复习MySQL①创建数据库及数据表
• 创建数据库:create database 数据库名称; – 例:创建名为test的测试数据库 create database test; • 查看创建好的数据库:show create data ...
- 使用Entity Framework通过code first方式创建数据库和数据表
开发环境 WIN10 Entity Framework6.0 MVC5.0 开发工具 VS2015 SqlServer2012 1.创建上下文Context继承DbContext,并创建其他的业 ...
- X删除数据表的新用法
删除数据表,可以这样进行,以前傻不拉唧的用sql去手动删除. DAL dal = ... dal.Db.CreateMetaData().SetSche ...
- MySQL 删除数据表
MySQL 删除数据表 MySQL中删除数据表是非常容易操作的, 但是你再进行删除表操作时要非常小心,因为执行删除命令后所有数据都会消失. 语法 以下为删除MySQL数据表的通用语法: DROP TA ...
随机推荐
- 【FAQ】HarmonyOS SDK 闭源开放能力 — PDF Kit
1.问题描述: 预览PDF文件,文档上所描述的loadDocument接口,可以返回文件的状态,并无法实现PDF的预览,是否有能预览PDF相关接口? 解决方案: 1.执行loadDocument进行加 ...
- 决策单调性DP
决策单调性DP是一个非常重要的DP类别.在决策点随枚举点增加单调不降时,可以有效地优化复杂度. 一般而言,决策点指的是对于一个 \(f[i]\),它的值需要从另一个值j中转移,而对于所有j,令 \(f ...
- SQL 日常练习 (十六)
最近接触了一波 RPA, 可以用来做一些数据采集的事情, 或者任意控制电脑上的软件, 感觉上是挺厉害的, 但我就是不想用, 尽管我尝试了一波, 最后还是放弃 了, 我还是喜欢纯代码的工作方式, 最为讨 ...
- 西湖论剑2025Misc—cscs
西湖论剑2025cscs详解 Cobalt Strike流量主要是找beacon,主要以两种形式呈现 ·一小段shellcode(几百个字节),通常叫做stager shellcode,这段代码下载整 ...
- 推荐一个kafka可视化客户端GUI工具(Kafka King)
Kafka King,比较新,只需要填写kafka连接地址就行,不需要什么zookeeper. 支持的功能也多: 查看集群节点列表(完成) 创建主题(支持批量).删除主题.支持根据消费者组统计每个to ...
- 使用Redis命令select切换数据库
redis的数据库个数是可以配置的,默认为16个.对应数据库的索引值为0 - (databases -1),即16个数据库,索引值为0-15. 默认使用第0个数据库,可使用select进行数据 ...
- java后端http大文件传输接口笔记
笔记 接口方 package com.chinaums.demo.example.controller; import org.springframework.web.bind.annotation. ...
- ArkUI-x跨平台Bridge最佳实践
bridge核心架构思想 平台桥接机制是ArkUI-X框架提供的⼀种ArkTs语⾔和平台原⽣语⾔(Java.OC)之间通信的机制,⽅便⼆者互相调⽤.需要说明的是,平台桥接机制必须在打开ArkUI界⾯时 ...
- (各种数组之间的互相转换)int 数组与List互相转换,object数组转换int数组
Stream流之List.Integer[].int[]相互转化 一.int[ ] 1.1.int[ ] 转 Integer[ ] public static void main(String[] a ...
- ChunJun支持异构数据源DDL转换与自动执行 丨DTMO 02期回顾(内含课程回放+课件)
导读: 4月26日晚,ChunJun项目核心成员.袋鼠云数栈大数据引擎开发专家渡劫为大家带来分享<ChunJun支持异构数据源DDL转换与自动执行>,我们将直播精华部分做了整理,带大家再次 ...