net core天馬行空系列-可用於依賴註入的,資料庫表和c#實體類互相轉換的介面實現

来源:https://www.cnblogs.com/hezp/archive/2022/06/10/16350382.html
-Advertisement-
Play Games

1.前言 hi,大家好,我是三合。作為一名程式猿,日常開發中,我們在接到需求以後,一般都會先構思一個模型,然後根據模型寫實體類,寫完實體類後在資料庫里建表,接著進行增刪改查, 也有第二種情況,就是有些人喜歡先在資料庫里建表,然後再添加實體類。前者是code First,後者是db First,如果數 ...


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)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們知道 Python 有很多運算符可以進行數學運算,如果是簡單的問題還好說,但是要處理一些相對複雜的問題也要我們自己一行一行手動的來編寫嗎?答案當然不是,Python 提供了 math 模塊對一些數學運算提供了支持。 1.簡介 math 模塊提供了對 C 標准定義的數學函數的訪問,但該模塊並不支持 ...
  • Skywalking介紹 Skywalking是一個國產的開源框架,2015年有吳晟個人開源,2017年加入Apache孵化器,國人開源的產品,主要開發人員來自於華為,2019年4月17日Apache董事會批准SkyWalking成為頂級項目,支持Java、.Net、NodeJs等探針,數據存儲支持 ...
  • 面試總結 最近棧長面試了一個 5 年經驗的 Java 程式員,簡歷和個人介紹都提到了精通 Java 多線程,於是我就問了幾個多線程方面的問題: 1、實現多線程有哪幾種方式,如何返回結果? 2、多個線程如何實現順序訪問? 3、兩個線程如何進行數據交換? 4、如何統計 5 個線程的運行總耗時? 5、如何 ...
  • 1 背景與挑戰 1.1 背景介紹 1.1.1 課程概述 瞭解雙11 的歷程 學習當前主流的電商系統架構體系 瞭解大促對電商系統的一些挑戰 面對大促活動,站在架構師角度思考,可能有哪些問題,如何應對 1.1.2 雙11歷程 (最早接觸雙11的年份?) ​ 起於2009年,剛開始的雙十一還並不出名,電商 ...
  • 一個工作了6年的粉絲,去阿裡面試,在第一面的時候被問到”Mysql的事務隔離級別“。 他竟然沒有回答上來,一直在私信向我訴苦。 我說,你只能怪年輕時候的你,那個時候不夠努力導致現在的你技術水平不夠。 好吧,關於這個問題,看看普通人和高手的回答。 普通人: Mysql的事務隔離級別它有四種 1.讀已提 ...
  • ArrayList是我們開發中最常用到的集合,但是很多人對它的源碼並不瞭解,導致面試時,面試官問的稍微深入的問題,就無法作答,今天我們一起來探究一下ArrayList源碼。 1. 簡介 ArrayList底層是數組,允許元素是null,能夠動態擴容 size、isEmpty、get、set、add ...
  • 0. 文章目的 面向C#新學者,介紹命名空間(namespace)的概念以及C#中的命名空間的相關內容。 1. 閱讀基礎 理解C與C#語言的基礎語法。 理解作用域概念。 2. 名稱衝突與命名空間 2.1 一個生活例子 假設貓貓頭在北京有一個叫AAA的朋友,在上海有兩個叫AAA的朋友,上海的兩個AAA ...
  • 現在有些客戶的業務數據還在使用傳統Excel來彙總信息,使用Email實現數據的傳遞與交互。iNeuOS線上報表工具的開發與發佈基本上可以替代傳統Excel的使用,並且用戶可以自定義模板、隨意查詢數據等。 ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...