1.前言

hi,大家好,我是三合。作为一名程序猿,日常开发中,我们在接到需求以后,一般都会先构思一个模型,然后根据模型写实体类,写完实体类后在数据库里建表,接着进行增删改查, 也有第二种情况,就是有些人喜欢先在数据库里建表,然后再添加实体类。前者是code First,后者是db First,如果数据库表和c#实体类可以互相转换的话,那么无疑将大大加快我们的开发速度,很幸运的是,当前依靠一些第三方建模软件或者是efcode就可以实现,我们以ef core举例,

1.1 ef core根据实体类生成数据库表

 //先定义一个我们自己的dbcontext
public class SqlServerDb : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = MyConfiguration.GetConfiguration("sqlServerDbConnectionString");
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new ArgumentNullException("sqlServer connectionString must not be null");
} optionsBuilder.UseSqlServer(connectionString);
} public DbSet<Customer> Customer { get; set; }
}
//然后在代码里这样调用
using (var database = new SqlServerDb())
{
database.Database.EnsureCreated();
}

那么efcore就会自动替我们生成一个数据库,库里面有一张表叫做Customer。整个过程无须我们手动干预。接着如果实体类有变更的话,也可以通过add-migration指令进行迁移。

1.2 ef core根据数据库表生成实体类

通过nuget安装了Microsoft.EntityFrameworkCore,Microsoft.EntityFrameworkCore.SqlServer,Microsoft.EntityFrameworkCore.Tools,Microsoft.VisualStudio.Web.CodeGeneration.Design这几个包之后,我们就可以在"程序包管理器控制台"中执行以下语句生成实体类:

Scaffold-DbContext "Data Source=192.168.12.34;Initial Catalog=数据库名称;User ID=登录名;Password=密码" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Force

1.3 ef core互相转换存在的一些缺陷

在我使用这种方式的过程中,发现了ef core的几个缺点。

  1. 当整个解决方案下,子项目比较多时,我要在程序包管理器控制台以及各个项目的切换上花费很多时间才能顺利执行命令,这就很麻烦。
  2. 我无法快速的获取某个实体类的建表语句(比如我希望只是生成sql,但不执行迁移),以及快速的从数据库表生成某个实体类(ef core可以自定义的根据表的集合生成指定的实体类,但是在项目中已存在该实体类的情况,要么直接生成失败,要么根据force条件直接覆盖项目中原有的实体类,这太粗暴了,有时候会将我手动修改过的实体类覆盖掉,我希望能自己控制这个生成的过程)。
  3. 很多数据库的efcore驱动不支持实体类注释迁移到数据库表的注释,这么重要的功能不知道为啥不实现。
  4. 整个ef core自成一派,没有提供接口给到我们,这样就无法将数据库表和c#实体类互转的功能集成到我们自己的系统里。
  5. 要获取迁移生成的sql以供备份,还要执行命令
Script-Migration -From 20220610.cs -To :"202206101.cs"

,执行这命令还要找到2个变更点,这还是很麻烦。

2 IDbGenerator横空出世

基于以上痛点,在找不到合适组件的情况下,我在SummerBoot框架中定义了IDbGenerator接口,实现了四种数据库(sqlserver,mysql,oracle(仅支持12),sqlite)表和c#实体类的互相转换。具体数据库表字段类型和c#类型之间的映射关系,我则是参考了各个ef core驱动里的实现,确保和ef core生成的表或者实体类相一致。接下来介绍整个使用过程。

2.1 通过nuget包添加SummerBoot引用

PM> Install-Package SummerBoot

2.2 在startup.cs类中注册服务

services.AddSummerBoot();

services.AddSummerBootRepository(it =>
{
//-----------以下为必填参数---------
//注册数据库类型,比如SqliteConnection,MySqlConnection,OracleConnection,SqlConnection
it.DbConnectionType = typeof(MySqlConnection);
//添加数据库连接字符串
it.ConnectionString = "";
});

2.3 根据实体类自动生成/修改数据库表

2.3.1 定义一个数据库实体类

实体类注解大部分来自于系统命名空间System.ComponentModel.DataAnnotations 和 System.ComponentModel.DataAnnotations.Schema,比如表名Table,列名Column,主键Key,主键自增DatabaseGenerated(DatabaseGeneratedOption.Identity),不映射该字段NotMapped,注释Description等,接下来以Customer为例

public class Customer
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { set; get; }
/// <summary>
/// 姓名
/// </summary>
[Description("姓名")]
public string Name { set; get; }
/// <summary>
/// 年龄
/// </summary>
[Description("年龄")]
public int Age { set; get; } /// <summary>
/// 会员号
/// </summary>
[Description("会员号")]
public string CustomerNo { set; get; } /// <summary>
/// 总消费金额
/// </summary>
[Description("总消费金额")]
public decimal TotalConsumptionAmount { set; get; }
}

2.3.2 注入IDbGenerator接口,调用GenerateSql方法生成建表或者修改表结构的sql

public class TestController : Controller
{
private readonly IDbGenerator dbGenerator; public TestController(IDbGenerator dbGenerator)
{
this.dbGenerator = dbGenerator;
} [HttpGet("GenerateSql")]
public async Task<IActionResult> GenerateSql()
{
var generateSqls = dbGenerator.GenerateSql(new List<Type>() { typeof(Customer) });
return Content("ok");
}
}

2.3.3.1 如果数据库中不存在该表名的表

这里以mysql为例,生成的sql如下:

CREATE TABLE test.`Customer` (
`Id` int NOT NULL AUTO_INCREMENT,
`Name` text NULL ,
`Age` int NOT NULL ,
`CustomerNo` text NULL ,
`TotalConsumptionAmount` decimal(18,2) NOT NULL ,
PRIMARY KEY (`Id`)
) ALTER TABLE test.`Customer` MODIFY `Name` text NULL COMMENT '姓名'
ALTER TABLE test.`Customer` MODIFY `Age` int NOT NULL COMMENT '年龄'
ALTER TABLE test.`Customer` MODIFY `CustomerNo` text NULL COMMENT '会员号'
ALTER TABLE test.`Customer` MODIFY `TotalConsumptionAmount` decimal(18,2) NOT NULL COMMENT '总消费金额'

虽然分成了2部分,没有生成的非常完美,但是不影响使用。

2.3.3.2 如果数据库中已存在该表名的表

那么生成的sql为,新增/更新字段的sql或者新增/更新注释的sql,为了避免数据丢失,不会有删除字段的sql,这里以Customer表举例,如果原本数据库里已经有了customer表,接着我们更新实体类,添加了一个地址的属性

public class Customer
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { set; get; }
/// <summary>
/// 姓名
/// </summary>
[Description("姓名")]
public string Name { set; get; }
/// <summary>
/// 年龄
/// </summary>
[Description("年龄")]
public int Age { set; get; } /// <summary>
/// 会员号
/// </summary>
[Description("会员号")]
public string CustomerNo { set; get; } /// <summary>
/// 总消费金额
/// </summary>
[Description("总消费金额")]
public decimal TotalConsumptionAmount { set; get; } /// <summary>
/// 地址
/// </summary>
[Description("地址")]
public string Address { set; get; }
}

,那么此时生成的sql为

ALTER TABLE test.`Customer` ADD `Address` text NULL
ALTER TABLE test.`Customer` MODIFY `Address` text NULL COMMENT '地址'

虽然分成了2部分,没有生成的非常完美,但是不影响使用。

2.3.3.3 可以选择执行这些sql

把生成sql和执行sql分成2部分操作,对于日常而言是更方便的,我们可以快速拿到要执行的sql,进行检查,确认没问题后,可以保存下来,在正式发布应用时,留给dba审查。执行sql的代码如下

var generateSqls = dbGenerator.GenerateSql(new List<Type>() { typeof(Customer) });
foreach (var sqlResult in generateSqls)
{
dbGenerator.ExecuteGenerateSql(sqlResult);
}

2.3.4 表的命名空间

sqlserver里命名空间即schemas,oracle里命名空间即模式,sqlite和mysql里命名空间即数据库,

如果要定义不同命名空间下的表,可类似添加[Table("CustomerWithSchema", Schema = "test1")]注解即可。

[Table("CustomerWithSchema", Schema = "test1")]
public class CustomerWithSchema
{
public string Name { set; get; }
public int Age { set; get; } = 0;
/// <summary>
/// 会员号
/// </summary>
public string CustomerNo { set; get; }
/// <summary>
/// 总消费金额
/// </summary>
public decimal TotalConsumptionAmount { set; get; }
}

那么此时生成的sql为

CREATE TABLE test1.`CustomerWithSchema` (
`Name` text NULL ,
`Age` int NOT NULL ,
`CustomerNo` text NULL ,
`TotalConsumptionAmount` decimal(18,2) NOT NULL
)

2.3.5 自定义实体类字段到数据库字段的类型映射或名称映射

这里统一使用column注解,如[Column("Age",TypeName = "float")]

public class Customer : BaseEntity
{
public string Name { set; get; } [Column("Age",TypeName = "float")]
public int Age { set; get; } = 0;
/// <summary>
/// 会员号
/// </summary>
public string CustomerNo { set; get; }
/// <summary>
/// 总消费金额
/// </summary>
public decimal TotalConsumptionAmount { set; get; }
}

生成的sql如下

CREATE TABLE `Customer2` (
`Id` int NOT NULL AUTO_INCREMENT,
`Name` text NULL ,
`Age` float NOT NULL ,
`CustomerNo` text NULL ,
`TotalConsumptionAmount` decimal(18,2) NOT NULL ,
PRIMARY KEY (`Id`)
)

2.4 根据数据库表自动生成实体类

2.4.1 注入IDbGenerator接口,调用GenerateCsharpClass方法生成c#类的文本

参数为数据库表名的集合和生成的实体类的命名空间

public class TestController : Controller
{
private readonly IDbGenerator dbGenerator; public TestController(IDbGenerator dbGenerator)
{
this.dbGenerator = dbGenerator;
} [HttpGet("GenerateClass")]
public async Task<IActionResult> GenerateClass()
{
var generateClasses = dbGenerator.GenerateCsharpClass(new List<string>() { "Customer" },"Test.Model");
return Content("ok");
}
}

生成的c#实体类如下,新建一个类文件并把文本黏贴进去即可

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Test.Model
{
[Table("Customer")]
public class Customer
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Id")]
public int Id { get; set; }
/// <summary>
///姓名
/// </summary>
[Column("Name")]
public string Name { get; set; }
/// <summary>
///年龄
/// </summary>
[Column("Age")]
public int Age { get; set; }
/// <summary>
///会员号
/// </summary>
[Column("CustomerNo")]
public string CustomerNo { get; set; }
/// <summary>
///总消费金额
/// </summary>
[Column("TotalConsumptionAmount")]
public decimal TotalConsumptionAmount { get; set; }
/// <summary>
///地址
/// </summary>
[Column("Address")]
public string Address { get; set; }
}
}

3.与仓储接口配合使用

首先定义仓储接口,接口的具体实现类会由SummerBoot框架自动生成

[AutoRepository]
public interface ICustomerRepository:IBaseRepository<Customer>
{
}

接着就可以直接注入使用,整个增删改查操作如下所示

[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
private readonly ICustomerRepository customerRepository;
private readonly IDbGenerator dbGenerator; public HomeController(ICustomerRepository customerRepository, IDbGenerator dbGenerator)
{
this.customerRepository = customerRepository;
this.dbGenerator = dbGenerator;
} [HttpGet("test")]
public IActionResult Test()
{
var results= dbGenerator.GenerateSql(new List<Type>() { typeof(Customer) });
var generateClasses = dbGenerator.GenerateCsharpClass(new List<string>() { "Customer" }, "Test.Model");
//执行ddl操作
foreach (var databaseSqlResult in results)
{
dbGenerator.ExecuteGenerateSql(databaseSqlResult);
} var cusotmer = new Customer()
{
Name = "三合",
Age = 3,
CustomerNo = "00001",
Address = "福建省",
TotalConsumptionAmount = 999
};
//增
customerRepository.Insert(cusotmer);
//改
cusotmer.Age = 5;
customerRepository.Update(cusotmer);
//也可以这样改
customerRepository.Where(it => it.Name == "三合").SetValue(it => it.Age, 6).ExecuteUpdate();
//查
var dbCustomer= customerRepository.FirstOrDefault(it => it.Name == "三合");
//删
customerRepository.Delete(dbCustomer);
//也可以这样删
customerRepository.Delete(it=>it.Name== "三合"); return Content("ok");
}
}

自动生成数据库表与仓储接口配合使用,就会使我们的整个开发过程顺畅无比,犹如行云流水。

4.结尾

更多用法,可参考SummerBoot文档,也可以加入QQ群:799648362反馈建议。同时各位看官,如果你觉得这篇文章还不错的话,请记得一键三连哦(推荐+关注+github star)

net core天马行空系列-可用于依赖注入的,数据库表和c#实体类互相转换的接口实现的更多相关文章

  1. net core天马行空系列-各大数据库快速批量插入数据方法汇总

    1.前言 hi,大家好,我是三合.我是怎么想起写一篇关于数据库快速批量插入的博客的呢?事情起源于我们工作中的一个需求,简单来说,就是有一个定时任务,从数据库里获取大量数据,在应用层面经过处理后再把结果 ...

  2. net core天马行空系列: 一个接口多个实现类,利用mixin技术通过自定义服务名,实现精准属性注入

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 哈哈哈哈,大家好,我 ...

  3. Java Web系列:Spring依赖注入基础

    一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...

  4. NET Core源代码通过Autofac实现依赖注入

    查看.NET Core源代码通过Autofac实现依赖注入到Controller属性   阅读目录 一.前言 二.使用Autofac 三.最后 回到目录 一.前言 在之前的文章[ASP.NET Cor ...

  5. net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 哈哈哈哈,大家好,我就是那个高产似母猪的三合,长久以来,我一直在思考,如何才能实现高效而简洁的仓储模式 ...

  6. .Net Core 3.0 内置依赖注入:举例

    原文:.Net Core 3.0 内置依赖注入:举例 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn ...

  7. net core天马行空系列:SummerBoot,将SpringBoot的先进理念与C#的简洁优雅合二为一

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 3.net core ...

  8. net core天马行空系列:移植Feign,结合Polly,实现回退,熔断,重试,超时,做最好用的声明式http服务调用端

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 3.net core ...

  9. 在.NET Core控制台程序中使用依赖注入

    之前都是在ASP.NET Core中使用依赖注入(Dependency Injection),昨天遇到一个场景需要在.NET Core控制台程序中使用依赖注入,由于对.NET Core中的依赖注入机制 ...

随机推荐

  1. vuejs兄弟组件之间的通信

    var Event = new Vue();//准备一个空的实例对象 //A组件 var A = { template: ` <div> <span>我是A组件的数据-> ...

  2. tracert命令简述

    1. 路由跟踪在线Tracert工具说明 Tracert(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路径.Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP ...

  3. mysql server_id的用途(主从等结构中)

    前言 我们都知道MySQL用server-id来唯一的标识某个数据库实例,并在链式或双主复制结构中用它来避免sql语句的无限循环.5.7需要同时设置server_id参数,8.0开始server_id ...

  4. 性能优化之html、css、js三者的加载顺序

    前言 我们知道一个页面通常由,html,css,js三部分组成,一般我们会把css文件放在head头部加载,而js文件则放在页面的最底部加载,想要知道为什么大家都会不约而同的按照这个标准进行构建页面, ...

  5. C++ 限定符Const和指针

    指向常量的指针 指向常量的指针不能用于其所指对象的值.若想存放常量对象的地址,只能使用指向常量的指针. const int a = 3; //a是个常量,其值不能改变 int *b = &a; ...

  6. HTML/CSS+JS制作一个高考倒计时页面

    2020-07-09更新 修复倒计时归零后出现负数的bug 自动切换至下一年日期 ##效果展示 前言 在B站上找视频学习的,勉强搞出来了,写下此篇文章作为笔记,也希望有更多感兴趣的人能够有所收获. ( ...

  7. [个人配置] VSCode Better Comments 扩展配置、高亮注释插件

    在VSCode IDE中,我的代码注释一般都有高亮颜色,那要怎么安装这个插件呢?

  8. Docker部署Nginx启动成功,浏览器拒绝访问

    今天下午部署完tomcat和mysql之后就接着部署Nginx,本以为Nginx也和之前两个一样简单,但是就因为标题这个问题,花费了我一个小时纠错. 过程复现: 解决完上一篇博客(https://ww ...

  9. php实验一专属跳转博文

    今天完成了php关于设计个人博客主页的实验一作业. 这是php实验一作业中博客的跳转链接页.  

  10. FreeRTOS --(7)任务管理之入门篇

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107019521 任务管理是操作系统中重中之重,不管什么 OS ,任务的调度管理都是核心 ...