前情提要 筆者這篇主要是想寫實體產生器的進階版本,怎麼會說是進階版本呢,若有用過EFCore的dotnet ef dbcontext scaffold
指令會知道,預設會是符合C#
命名規則,即Pascal Case
,每個英文單字連在一起,每個單字的第一個字為大寫。scaffold
指令還特別開放options:-use-database-names
,即可保留其資料庫中的命名。
筆者當初也沒有特別注意這些細節,因為是有踩過一些雷,例:製作前端使用ViewModel
時,從上述scaffold
指令產出的Entities
複製其對應的屬性宣告,再補一些額外的屬性,然後透過Dapper
套件Binding屬性時發現,怎麼有些屬性明明資料庫就有回傳,怎麼沒有binding成功,仔細一看,資料庫端是使用下底線,到C#端時scaffold
會把下底線去掉,因此直接從Entities
中複製貼上則會是不一樣的屬性名稱,自從那一次後,就注意到該細節。
講到此篇主題,剛好手邊新專案,資料庫使用MySQL
,筆者想保持資料庫命名規則,主要是有兩點
另一方面,筆者透過SqlSugarCore
提供的實體產生相關API產生相關實體時,又想保持C#命名規則,最好是跟EFCore scaffold一樣可以保有複數機制,這為此篇主要解決目的。
內容 關於此篇想達成的目的,主要工作有三個
SqlSugarCore自定義命名方法
字串轉PascalCase命名規則
字串轉複數命名機制
SqlSugarCore自定義命名方法 SqlSugarCore本身有提供產生實體時可以做客製化,轉換邏輯自定義,或者產生實體時的Class及Property客製化,對於筆者這邊的情境來說只要能Class及Property客製化宣告即可,筆者這邊主要參考
https://www.donet5.com/Home/Doc?typeId=1207
直接來看Code比較有感覺
1 2 3 4 5 6 7 8 9 10 foreach (var item in db.DbMaintenance.GetTableInfoList()){ string entityName = item.Name.ToUpper(); db.MappingTables.Add(entityName , item.Name); foreach (var col in db.DbMaintenance.GetColumnInfosByTableName(item.Name)) { db.MappingColumns.Add(col.DbColumnName.ToUpper() , col.DbColumnName, entityName); } } db.DbFirst.IsCreateAttribute().CreateClassFile("c:\\Demo\\8" , "Models" );
主要是有兩個地方要改寫
entityName的轉換邏輯:即Table名稱轉換成Class Name邏輯
DbColumnName的轉換:即Column名稱轉換成Property Name邏輯
SqlSugarCore官網說明這邊主要是簡單改寫成全大寫的形式,筆者介紹完下面兩個章節後再回來修改這邊的程式邏輯。
PascalCase轉換 筆者這邊是完全遵照MySQL命名規則,所以簡單來說一下邏輯是這樣
用下底線_符號切割字串
將上述切割出來的字串,將第一個字母轉大寫
最後將所有字串Join回去
1 2 3 4 5 6 7 8 9 10 11 public static class StringExtension { public static string ToPascalCase (this string value ) { var words = value .Split(new [] { "_" , "-" , " " }, StringSplitOptions.RemoveEmptyEntries); words = words .Select(word => char .ToUpper(word[0 ]) + word.Substring(1 )) .ToArray(); return string .Join(string .Empty, words); } }
透過Linqpad測試一下其轉換結果
1 2 3 4 5 6 7 8 void Main (){ var testStr = "vendor_id" ; testStr.ToPascalCase().Dump(); }
複數命名轉換 筆者這邊有查到 .net framework
時有內建的服務PluralizationService
,但到轉換到dotnet core
時,只查得到相關套件,就以google
到的第一個套件為主去試看看吧。
1 dotnet add package PluralizeService.Core --version 1.2.21147.2
接著撰寫程式吧,筆者沿用上章節所用到的StringExtension,完善它。
1 2 3 4 5 6 7 8 public static class StringExtension { public static string Pluralize (this string value ) { return PluralizationProvider.Pluralize(value ); } }
透過Linqpad測試一下其效果
1 2 3 4 5 6 7 8 void Main (){ var testStr = "customer" ; testStr.ToPascalCase().Pluralize().Dump(); }
當然該套件不只變成複數,將複數轉換成單數也是可以的,且一行指令就完成,滿無腦套用的。
實體產生器改造 最後要來套用在轉換真實資料庫上,筆者就改造之前的文章[DotnetCore]ORM系列-SqlSugar:實體產生器-進階 裡的程式內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 using CommandLine;using PluralizeService.Core;using SqlSugar;Parser.Default.ParseArguments<Options>(args).WithParsed(Run); static void Run (Options option ){ using var _db = new SqlSugarScope(new ConnectionConfig() { DbType = SqlSugar.DbType.MySql, ConnectionString = option.ConnectionString, InitKeyType = InitKeyType.Attribute, IsAutoCloseConnection = true }); foreach (var item in _db.DbMaintenance.GetTableInfoList()) { string entityName = item.Name.ToPascalCase().Pluralize(); _db.MappingTables.Add(entityName, item.Name); foreach (var col in _db.DbMaintenance.GetColumnInfosByTableName(item.Name)) { _db.MappingColumns.Add(col.DbColumnName.ToPascalCase(), col.DbColumnName, entityName); } } _db.DbFirst.IsCreateAttribute().CreateClassFile(option.ModelPath, option.ModelNameSpace); } public class Options { [Option('c', "connectionstring" , Required = true, HelpText = "資料庫連線字串" ) ] public string ConnectionString { get ; set ; } [Option('p', "path" , Required = true, HelpText = "Models檔案放置路徑" ) ] public string ModelPath { get ; set ; } [Option('n', "namespace" , Required = true, HelpText = "Model命名空間" ) ] public string ModelNameSpace { get ; set ; } } public static class StringExtension { public static string ToPascalCase (this string value ) { var words = value .Split(new [] { "_" , "-" , " " }, StringSplitOptions.RemoveEmptyEntries); words = words .Select(word => char .ToUpper(word[0 ]) + word.Substring(1 )) .ToArray(); return string .Join(string .Empty, words); } public static string Pluralize (this string value ) { return PluralizationProvider.Pluralize(value ); } }
最終呈現效果 筆者先貼上其資料庫欄位樣子
1 2 3 4 5 6 7 8 9 CREATE TABLE `product` ( `id` bigint NOT NULL AUTO_INCREMENT, `brand` varchar (50 ) DEFAULT NULL , `timing_kit_no` text, `customer_product_no` text, `memo` text, `weight` decimal (8 ,3 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 7 DEFAULT CHARSET= utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
透過dotnet run指令產生其對應的實體
最終得到的實體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 / / / < summary> / / / / / / < / summary> [SugarTable("product")] public partial class Products { public Products(){ } / / / < summary> / / / Desc : / / / Default : / / / Nullable:False / / / < / summary> [SugarColumn(IsPrimaryKey= true ,IsIdentity= true ,ColumnName= "id")] public long Id {get ;set ;} / / / < summary> / / / Desc : / / / Default : / / / Nullable:True / / / < / summary> [SugarColumn(ColumnName= "brand")] public string Brand {get ;set ;} / / / < summary> / / / Desc : / / / Default : / / / Nullable:True / / / < / summary> [SugarColumn(ColumnName= "timing_kit_no")] public string TimingKitNo {get ;set ;} / / / < summary> / / / Desc : / / / Default : / / / Nullable:True / / / < / summary> [SugarColumn(ColumnName= "customer_product_no")] public string CustomerProductNo {get ;set ;} / / / < summary> / / / Desc : / / / Default : / / / Nullable:True / / / < / summary> [SugarColumn(ColumnName= "memo")] public string Memo {get ;set ;} / / / < summary> / / / Desc : / / / Default : / / / Nullable:True / / / < / summary> [SugarColumn(ColumnName= "weight")] public decimal ? Weight {get ;set ;} }
結論 沒想到小小的實體產生器,也有如此大的學問,且透過SqlSugarCore
產生出來的實體,預設為Partial Class
,因此可以宣告一個Products.Partial.cs
來撰寫其特定方法,與自動產生的檔案做切割,自定義的Method不會被影響,筆者覺得Partial Class
真的是一個好物,只可惜不可跨assembly
使用,不過它套用的前提是一樣的Namespace
下,不可跨assembly
似乎也是合理,這篇就到這邊了,希望對有這個需求的各位有所幫助。
參考