.NET Core-可用于依賴注入的、數據庫表和實體類互相轉換的接口實現
一、前言
作為一名程序猿,日常開發(fā)中,我們在接到需求以后,一般都會先構思一個模型,然后根據模型寫實體類,寫完實體類后在數據庫里建表,接著進行增刪改查, 也有第二種情況,就是有些人喜歡先在數據庫里建表,然后再添加實體類。
前者是code First,后者是db First,如果數據庫表和C#實體類可以互相轉換的話,那么無疑將大大加快我們的開發(fā)速度,很幸運的是,當前依靠一些第三方建模軟件或者是EF Core就可以實現,我們以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這幾個包之后,我們就可以在"程序包管理器控制臺"中執(zhí)行以下語句生成實體類:
Scaffold-DbContext "Data Source=192.168.12.34;Initial Catalog=數據庫名稱;User ID=登錄名;Password=密碼" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Force
1.3、EF Core互相轉換存在的一些缺陷
在我使用這種方式的過程中,發(fā)現了ef core的幾個缺點。
1、當整個解決方案下,子項目比較多時,我要在程序包管理器控制臺以及各個項目的切換上花費很多時間才能順利執(zhí)行命令,這就很麻煩。
2、我無法快速的獲取某個實體類的建表語句(比如我希望只是生成sql,但不執(zhí)行遷移),以及快速的從數據庫表生成某個實體類(ef core可以自定義的根據表的集合生成指定的實體類,但是在項目中已存在該實體類的情況,要么直接生成失敗,要么根據force條件直接覆蓋項目中原有的實體類,這太粗暴了,有時候會將我手動修改過的實體類覆蓋掉,我希望能自己控制這個生成的過程)。
3、多數據庫的ef core驅動不支持實體類注釋遷移到數據庫表的注釋,這么重要的功能不知道為啥不實現。
4、整個ef core自成一派,沒有提供接口給到我們,這樣就無法將數據庫表和c#實體類互轉的功能集成到我們自己的系統(tǒng)里。
5、要獲取遷移生成的sql以供備份,還要執(zhí)行命令
Script-Migration -From 20220610.cs -To :"202206101.cs"
,執(zhí)行這命令還要找到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、定義一個數據庫實體類
實體類注解大部分來自于系統(tǒng)命名空間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 可以選擇執(zhí)行這些SQL
把生成sql和執(zhí)行sql分成2部分操作,對于日常而言是更方便的,我們可以快速拿到要執(zhí)行的sql,進行檢查,確認沒問題后,可以保存下來,在正式發(fā)布應用時,留給dba審查。執(zhí)行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 自定義實體類字段到數據庫字段的類型映射或名稱映射
這里統(tǒng)一使用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; }
}
}
三、與倉儲接口配合使用
首先定義倉儲接口,接口的具體實現類會由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");
//執(zhí)行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");
}
}
自動生成數據庫表與倉儲接口配合使用,就會使我們的整個開發(fā)過程順暢無比,猶如行云流水。
四、結尾
SummerBoot文檔:https://github.com/TripleView/SummerBoot
更多用法可參考,如果你覺得這篇文章還不錯的話,記得推薦。
轉自: 三合視角
鏈接:cnblogs.com/hezp/p/16350382.html
