hi,大家好,我是三合。作為一名程式猿,日常開發中,我們在接到需求以後,一般都會先構思一個模型,然後根據模型寫實體類,寫完實體類後在資料庫裡建表,接著進行增刪改查, 也有第二種情況,就是有些人喜歡先在資料庫裡建表,然後再新增實體類。前者是code First,後者是db First,如果資料庫表和c#實體類可以互相轉換的話,那麼無疑將大大加快我們的開發速度,很幸運的是,當前依靠一些第三方建模軟體或者是efcode就可以實現,我們以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指令進行遷移。
通過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
在我使用這種方式的過程中,發現了ef core的幾個缺點。
Script-Migration -From 20220610.cs -To :"202206101.cs"
,執行這命令還要找到2個變更點,這還是很麻煩。
基於以上痛點,在找不到合適元件的情況下,我在SummerBoot框架中定義了IDbGenerator介面,實現了四種資料庫(sqlserver,mysql,oracle(僅支援12),sqlite)表和c#實體類的互相轉換。具體資料庫表欄位型別和c#型別之間的對映關係,我則是參考了各個ef core驅動裡的實現,確保和ef core生成的表或者實體類相一致。接下來介紹整個使用過程。
PM> Install-Package SummerBoot
services.AddSummerBoot();
services.AddSummerBootRepository(it =>
{
//-----------以下為必填引數---------
//註冊資料庫型別,比如SqliteConnection,MySqlConnection,OracleConnection,SqlConnection
it.DbConnectionType = typeof(MySqlConnection);
//新增資料庫連線字串
it.ConnectionString = "";
});
實體類註解大部分來自於系統名稱空間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; }
}
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");
}
}
這裡以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部分,沒有生成的非常完美,但是不影響使用。
那麼生成的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部分,沒有生成的非常完美,但是不影響使用。
把生成sql和執行sql分成2部分操作,對於日常而言是更方便的,我們可以快速拿到要執行的sql,進行檢查,確認沒問題後,可以儲存下來,在正式釋出應用時,留給dba審查。執行sql的程式碼如下
var generateSqls = dbGenerator.GenerateSql(new List<Type>() { typeof(Customer) });
foreach (var sqlResult in generateSqls)
{
dbGenerator.ExecuteGenerateSql(sqlResult);
}
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
)
這裡統一使用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`)
)
引數為資料庫表名的集合和生成的實體類的名稱空間
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");
//執行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");
}
}
自動生成資料庫表與倉儲介面配合使用,就會使我們的整個開發過程順暢無比,猶如行雲流水。
更多用法,可參考SummerBoot檔案,也可以加入QQ群:799648362反饋建議。同時各位看官,如果你覺得這篇文章還不錯的話,請記得一鍵三連哦(推薦+關注+github star)