【BCVP升级】泛型主键的使用
(圖片來源于SqlSugar官網,5年5.0)
大家假期已經結束了吧,還有80天左右就要到2021年了,你準備好了么?BCVP(Blog.Core&Vue Project)項目已經開源2年多,從來沒有停更過,網上出現了很多仿品,當然這是好事兒,我從一開始也是這么鼓勵大家的,第一要學習知識點,第二如果學會了自己動手搭一搭,這樣不僅自己有了一定的深入理解,從全局上鞏固,另外也可以對他人有一個借鑒和參考的不同版本,不過還是建議可以稍微稍稍的說一下,靈感/思路/學習受老張的幫助、影響和借鑒,想必你也明白,一邊開源,一邊講解,一邊建立社區回答問題,是一個常人無非想象的毅力。最近打算成立一個基于BCVP的開發者社區,感興趣的可以留言,一起來個Business版本,兩三個人即可,是那種真的想設計的,看緣分吧。
今天繼續推進BCVP項目的往下進行,新開了一個需求,這個需求來自于網友的提問:目前BlogCore項目默認使用的是int作為主鍵,并自增,平時開發的時候int或者long這個都是很常見的,但是如果說,我就不想用int,習慣了Guid,當然也為了更方便遷移數據,因為int會亂序,特別是在多庫的時候。那這個時候如果我想把int主鍵,改成guid,工作量是多大,需要改多少地方,怎么處理邏輯,前端修改哪些地方,等等等等。
所以我就嘗試了這個新課題:使用泛型主鍵,這樣拿到這個項目的時候,自己修改下主鍵類型,就可以運行了,不過目前還沒有百分百完善,int主鍵已經調通,其他類型主鍵,比如Guid或者自定義string還沒有完成生產化,但是放心,我肯定會完善的,最終的目的是下載項目后,可以滿足自定義配置。
做這個需求的目的,一是為了靈活框架,二也是為了給大家提供一個思路。
別一上來就說沒用,你可以不用我的框架,但是這個思路還是可以了解下的,平時ORM中是如何控制的,而且泛型在項目開發中的作用特別大。好啦,下邊就先簡單說一下思路吧,當然離不開SqlSugar的強大支持。
1、自定義特性
配置服務SqlsugarSetup
既然要實現泛型主鍵,那我們就需要對主鍵進行處理,因為只有int類型的主鍵才需要自增,其他類型的是不需要的,當然如果在非int類型的主鍵上配置自增了也是會報錯的。
在SqlsugarSetup.cs服務類中,配置自定義特性:
listConfig.Add(new ConnectionConfig(){ConfigId = m.ConnId.ObjToString().ToLower(),ConnectionString = m.Connection,DbType = (DbType)m.DbType,IsAutoCloseConnection = true,IsShardSameThread = true,AopEvents = new AopEvents{OnLogExecuting = (sql, p) =>{if (Appsettings.app(new string[] { "AppSettings", "SqlAOP", "Enabled" }).ObjToBool()){Parallel.For(0, 1, e =>{MiniProfiler.Current.CustomTiming("SQL:", GetParas(p) + "【SQL語句】:" + sql);LogLock.OutSql2Log("SqlLog", new string[] { GetParas(p), "【SQL語句】:" + sql });});}}},MoreSettings = new ConnMoreSettings(){//IsWithNoLockQuery = true,IsAutoRemoveDataCache = true},// 從庫SlaveConnectionConfigs = listConfig_Slave,// 自定義特性ConfigureExternalServices = new ConfigureExternalServices(){EntityService = (property, column) =>{if (column.IsPrimarykey && property.PropertyType == typeof(int)){column.IsIdentity = true;}}},InitKeyType = InitKeyType.Attribute}核心的就是ConfigureExternalServices這個方法,如果是主鍵,并且是int,才會增加它的自增屬性,否則不處理。
■?■■■■
修改實體基類RootEntityTkey
這里我重寫了一個基于泛型主鍵的實體基類RootEntityTkey,因為有了上邊的配置,所以就不需要在主鍵上增加自增了,只需要配置一個屬性:是否為主鍵即可,因為肯定不為空,另一個參數IsNullable可以不寫:
現在配置好了自定義特性,那就開始今天的重頭戲了——設計泛型。
■?■■■■
2、設計泛型主鍵結構
實體基類增加泛型參數
上邊我們已經重新設計了一個實體基類,在它的基礎上,我們可以先增加一個泛型參數:
這都是很簡單的,就是新增了一個參數Tkey,我就不多說了,只要是用過泛型的肯定一眼就能明白,如果看不明白,可以學習下基礎知識了。
這里有一個小疑問,你可能會說,那我int類型有一個數字自增,但是如果其他類型的時候,如何配置默認值呢,別擔心Sqlsugar已經提供了Guid的默認值,你可以查看源碼,是這么設計的:
這樣的話,我們的實體類的如果是Guid,就算是一個空的對象實例,存入的時候也會有值,具體的寫法我下文會舉例說明的。
定義好了基類,那我們就需要動手數據庫實體類了,可能稍微復雜一點,因為會涉及另一個重要的概念。
■?■■■■
普通實體模型繼承基類,并傳遞參數
剛剛已經定義好了泛型基類,那現在我們來設計下實體類,這里有兩個情況,一種是普通的類結構,比如角色表自己不和其他交互,只有主鍵Id,另一種是有外鍵的復雜的類結構,比如用戶角色表中,有Uid和Rid。咱們先說下普通類型的。
/// <summary> /// 角色表 /// </summary> public class Role : RootEntityTkey<int> {// 因為繼承了RootEntityTkey,所以就不用寫主鍵Id了/// <summary>///獲取或設置是否禁用,邏輯上的刪除,非物理刪除/// </summary>[SugarColumn(IsNullable = true)]public bool? IsDeleted { get; set; }/// <summary>/// 角色名/// </summary>[SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)]public string Name { get; set; }// 等等其他的字段...}這里用角色表Role舉例,直接繼承父類RootEntityTkey<int>,然后定義該實體除主鍵以外的屬性和字段等即可,還是很簡單的,也是很普通的寫法。
■?■■■■
復雜的實體模型
上邊寫了簡單的方案,但是平時開發肯定不會是這樣的,不免會出現有關系的情況,也就是外鍵的問題,比如用戶角色關系表UserRole,它里邊除了主鍵Id以外,肯定也會包含Uid和Rid,那如何設計呢,如果單純的繼承RootEntityTkey肯定是不行的,因為如果這么操作了,這個關系表中肯定就不能和User表或者Role表保持一致了,所以這三個字段都應該設計成泛型的格式,那如何設計的?
我參照著實體泛型基類,又單獨針對特定的有外鍵需求的實體,抽離了一個中間父類,請注意我的命名:實體類-->父類(非必須)-->泛型基類,用UserRole來舉例。
1、還是先定義UserRole的實體類內容
2、然后抽離父類,對外鍵和Pid等單純處理
3、最后將抽離的父類來繼承泛型基類
這樣不僅可以滿足這種外鍵的問題,也可以來處理一些特殊的情況,比如Pid,你想一下,主鍵如果都泛型了,總不能Pid父id這種還是int吧,這里用接口表的抽離父類舉例:
BlogCore項目我已經修改完成,最終的結果是這樣的:
核心的就是RootTkey這個文件夾下,就是這次修改的主要部分,其他的實體模型基本不用修改,只需要繼承特定的專屬父類/基類即可: RootEntityTkey<Tkey>。
■?■■■■
3、其他重要提醒
不要把抽離的父類生成到數據庫
在BlogCore項目中,我用的是自動CodeFirst并可以生成種子數據,當生成表結構的時候,我是根據命名空間來處理的,你在設計抽離的父類,比如UserRoleRoot<Tkey>的時候,注意修改命名空間,別生成到了數據庫里,當然肯定也生成不進去,會報錯的,這里只是提個醒,因為是CodeFirst的邏輯是根據命名空間:
// 創建數據庫表,遍歷指定命名空間下的class, // 注意不要把其他命名空間下的也添加進來。 Console.WriteLine("Create Tables..."); var modelTypes = from t in Assembly.GetExecutingAssembly().GetTypes()where t.IsClass && t.Namespace == "Blog.Core.Model.Models"select t; modelTypes.ToList().ForEach(t => {if (!myContext.Db.DbMaintenance.IsAnyTable(t.Name)){Console.WriteLine(t.Name);myContext.Db.CodeFirst.InitTables(t);} });當然,你也可以自己優化下,比如來個特性,或者繼承一個接口啥的來限制只有實體模型才可以生成到數據庫等等,看你的需要了。
■?■■■■
生成種子數據的時候,反序列化要注意
我也同時優化了種子數據json的反序列化,比如整型用的是0,不是"0",這樣的問題。
然后反序列化的方法也改用Newtonsoft.Json組件了,之前我之前自己寫的,在反序列化的時候有不識別null的問題,所以需要配置一個setting來處理掉null,具體的代碼,可以查看DBSeed.cs 這個文件。這里舉一個例子:
JsonSerializerSettings setting = new JsonSerializerSettings();JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>{//日期類型默認格式化處理setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;setting.DateFormatString?=?"yyyy-MM-dd?HH:mm:ss";//空值處理setting.NullValueHandling?=?NullValueHandling.Ignore;//setting.Converters.Add(new BoolConvert("是,否"));return setting;});if (!await myContext.Db.Queryable<TopicDetail>().AnyAsync()) {var data = JsonConvert.DeserializeObject<List<TopicDetail>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "TopicDetail"), Encoding.UTF8), setting);myContext.GetEntityDB<TopicDetail>().InsertRange(data);Console.WriteLine("Table:TopicDetail created success!"); } else {Console.WriteLine("Table:TopicDetail already exists..."); }■?■■■■
項目如何初始化自定義主鍵類型
現在我的項目中,已經完全配置好了int類型的模式了,如果你想使用Guid的話,應該如何操作呢,很簡單,只需要直接修改下泛型參數就行,這里用Advertisement舉例子說明下:
1、修改泛型參數為Guid:
public class Advertisement : RootEntityTkey<Guid> {// 屬性字段等... }2、執行Add操作
var ad = await _advertisementServices.Add(new Advertisement());3、注意倉儲執行方法
因為之前我們都是使用的int作為主鍵,然后用的.ExecuteReturnIdentityAsync()方法,這樣返回的是對應的id。
但是現在用了Guid以后,就不能這么用了,因為這樣使用的話,這個方法是無效的.ExecuteReturnIdentityAsync(),不僅不會正常的返回id值,也無非自動生成Guid的默認值,你可以使用.ExecuteCommandAsync(),當然可以直接使用.ExecuteReturnEntityAsync()這個方法,來返回實體,然后從實體里,獲取對應的Id,這樣的話,不論是int還是Guid,都能返回出來了。
4、查看效果
設置了Guid以后,就可以看看效果了,上邊的0000-000-0000-000這樣的值,就是因為使用的.ExecuteReturnIdentityAsync(),下邊的是正常的使用Command方法,或者直接ReturnEntity的方法。
總體來說還是很方便的。
好啦,今天的分享暫時就是這樣的,希望能提供一個思路,無論是對BCVP項目,還是你自己的項目。
? end?
BCVP開發者社區推薦
歡迎你來
總結
以上是生活随笔為你收集整理的【BCVP升级】泛型主键的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Standard 来日苦短去日
- 下一篇: Dotnet Core使用特定的SDKR