当前位置: 首页 > news >正文

【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 那样方便?

 

http://www.wuyegushi.com/news/438.html

相关文章:

  • 亚马逊机器学习大学推出负责任AI课程 - 聚焦AI偏见缓解与公平性实践
  • FFmpeg开发笔记(七十八)采用Kotlin+Compose的NextPlayer播放器
  • 4.5.4 预测下一个PC
  • 第十六日
  • 2025“钉耙编程”中国大学生算法设计暑期联赛(3)
  • VMware Windows Linux Macos网盘下载
  • ZBrush 2025 中文版免费下载,附图文安装指南,小白也能快速上手!
  • k8s network
  • hyprland初尝试
  • 正则表达式 更新常用则表达式-----loading
  • 幼儿园小班线段树
  • 树02
  • 深入ADC采样
  • 学习笔记:MySQL :eq_range_index_dive_limit参数
  • Python字符串知识点总结
  • SQL Server 2025年7月更新 - 修复 CVE-2025-49718 Microsoft SQL Server 信息泄露漏洞
  • 读书笔记:Oracle数据库内存结构:系统全局区(SGA)详解
  • 小飞标签
  • 服务器配置的精细化控制(3960)
  • TCP连接优化的实战经验(7340)
  • 家庭主妇人到中年的生活困境很难突破防
  • 中间件架构的优雅实现(0454)
  • 梦醒时分
  • Hyperlane框架最全教学(6165)
  • 并发处理能力的巅峰对决:异步编程的艺术(3501)
  • 实战项目:全栈在线群聊系统(7048)
  • HTTP响应处理的灵活设计(0782)
  • Rust异步Web框架性能突破之路(6359)
  • 服务器配置的精细化控制(7138)
  • 内存使用效率的终极对决:零拷贝技术的实战应用(0345)