【BCVP升级】泛型主键的使用
(圖片來(lái)源于SqlSugar官網(wǎng),5年5.0)
大家假期已經(jīng)結(jié)束了吧,還有80天左右就要到2021年了,你準(zhǔn)備好了么?BCVP(Blog.Core&Vue Project)項(xiàng)目已經(jīng)開源2年多,從來(lái)沒有停更過(guò),網(wǎng)上出現(xiàn)了很多仿品,當(dāng)然這是好事兒,我從一開始也是這么鼓勵(lì)大家的,第一要學(xué)習(xí)知識(shí)點(diǎn),第二如果學(xué)會(huì)了自己動(dòng)手搭一搭,這樣不僅自己有了一定的深入理解,從全局上鞏固,另外也可以對(duì)他人有一個(gè)借鑒和參考的不同版本,不過(guò)還是建議可以稍微稍稍的說(shuō)一下,靈感/思路/學(xué)習(xí)受老張的幫助、影響和借鑒,想必你也明白,一邊開源,一邊講解,一邊建立社區(qū)回答問(wèn)題,是一個(gè)常人無(wú)非想象的毅力。最近打算成立一個(gè)基于BCVP的開發(fā)者社區(qū),感興趣的可以留言,一起來(lái)個(gè)Business版本,兩三個(gè)人即可,是那種真的想設(shè)計(jì)的,看緣分吧。
今天繼續(xù)推進(jìn)BCVP項(xiàng)目的往下進(jìn)行,新開了一個(gè)需求,這個(gè)需求來(lái)自于網(wǎng)友的提問(wèn):目前BlogCore項(xiàng)目默認(rèn)使用的是int作為主鍵,并自增,平時(shí)開發(fā)的時(shí)候int或者long這個(gè)都是很常見的,但是如果說(shuō),我就不想用int,習(xí)慣了Guid,當(dāng)然也為了更方便遷移數(shù)據(jù),因?yàn)閕nt會(huì)亂序,特別是在多庫(kù)的時(shí)候。那這個(gè)時(shí)候如果我想把int主鍵,改成guid,工作量是多大,需要改多少地方,怎么處理邏輯,前端修改哪些地方,等等等等。
所以我就嘗試了這個(gè)新課題:使用泛型主鍵,這樣拿到這個(gè)項(xiàng)目的時(shí)候,自己修改下主鍵類型,就可以運(yùn)行了,不過(guò)目前還沒有百分百完善,int主鍵已經(jīng)調(diào)通,其他類型主鍵,比如Guid或者自定義string還沒有完成生產(chǎn)化,但是放心,我肯定會(huì)完善的,最終的目的是下載項(xiàng)目后,可以滿足自定義配置。
做這個(gè)需求的目的,一是為了靈活框架,二也是為了給大家提供一個(gè)思路。
別一上來(lái)就說(shuō)沒用,你可以不用我的框架,但是這個(gè)思路還是可以了解下的,平時(shí)ORM中是如何控制的,而且泛型在項(xiàng)目開發(fā)中的作用特別大。好啦,下邊就先簡(jiǎn)單說(shuō)一下思路吧,當(dāng)然離不開SqlSugar的強(qiáng)大支持。
1、自定義特性
配置服務(wù)SqlsugarSetup
既然要實(shí)現(xiàn)泛型主鍵,那我們就需要對(duì)主鍵進(jìn)行處理,因?yàn)橹挥衖nt類型的主鍵才需要自增,其他類型的是不需要的,當(dāng)然如果在非int類型的主鍵上配置自增了也是會(huì)報(bào)錯(cuò)的。
在SqlsugarSetup.cs服務(wù)類中,配置自定義特性:
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語(yǔ)句】:" + sql);LogLock.OutSql2Log("SqlLog", new string[] { GetParas(p), "【SQL語(yǔ)句】:" + sql });});}}},MoreSettings = new ConnMoreSettings(){//IsWithNoLockQuery = true,IsAutoRemoveDataCache = true},// 從庫(kù)SlaveConnectionConfigs = listConfig_Slave,// 自定義特性ConfigureExternalServices = new ConfigureExternalServices(){EntityService = (property, column) =>{if (column.IsPrimarykey && property.PropertyType == typeof(int)){column.IsIdentity = true;}}},InitKeyType = InitKeyType.Attribute}核心的就是ConfigureExternalServices這個(gè)方法,如果是主鍵,并且是int,才會(huì)增加它的自增屬性,否則不處理。
■?■■■■
修改實(shí)體基類RootEntityTkey
這里我重寫了一個(gè)基于泛型主鍵的實(shí)體基類RootEntityTkey,因?yàn)橛辛松线叺呐渲?#xff0c;所以就不需要在主鍵上增加自增了,只需要配置一個(gè)屬性:是否為主鍵即可,因?yàn)榭隙ú粸榭?#xff0c;另一個(gè)參數(shù)IsNullable可以不寫:
現(xiàn)在配置好了自定義特性,那就開始今天的重頭戲了——設(shè)計(jì)泛型。
■?■■■■
2、設(shè)計(jì)泛型主鍵結(jié)構(gòu)
實(shí)體基類增加泛型參數(shù)
上邊我們已經(jīng)重新設(shè)計(jì)了一個(gè)實(shí)體基類,在它的基礎(chǔ)上,我們可以先增加一個(gè)泛型參數(shù):
這都是很簡(jiǎn)單的,就是新增了一個(gè)參數(shù)Tkey,我就不多說(shuō)了,只要是用過(guò)泛型的肯定一眼就能明白,如果看不明白,可以學(xué)習(xí)下基礎(chǔ)知識(shí)了。
這里有一個(gè)小疑問(wèn),你可能會(huì)說(shuō),那我int類型有一個(gè)數(shù)字自增,但是如果其他類型的時(shí)候,如何配置默認(rèn)值呢,別擔(dān)心Sqlsugar已經(jīng)提供了Guid的默認(rèn)值,你可以查看源碼,是這么設(shè)計(jì)的:
這樣的話,我們的實(shí)體類的如果是Guid,就算是一個(gè)空的對(duì)象實(shí)例,存入的時(shí)候也會(huì)有值,具體的寫法我下文會(huì)舉例說(shuō)明的。
定義好了基類,那我們就需要?jiǎng)邮謹(jǐn)?shù)據(jù)庫(kù)實(shí)體類了,可能稍微復(fù)雜一點(diǎn),因?yàn)闀?huì)涉及另一個(gè)重要的概念。
■?■■■■
普通實(shí)體模型繼承基類,并傳遞參數(shù)
剛剛已經(jīng)定義好了泛型基類,那現(xiàn)在我們來(lái)設(shè)計(jì)下實(shí)體類,這里有兩個(gè)情況,一種是普通的類結(jié)構(gòu),比如角色表自己不和其他交互,只有主鍵Id,另一種是有外鍵的復(fù)雜的類結(jié)構(gòu),比如用戶角色表中,有Uid和Rid。咱們先說(shuō)下普通類型的。
/// <summary> /// 角色表 /// </summary> public class Role : RootEntityTkey<int> {// 因?yàn)槔^承了RootEntityTkey,所以就不用寫主鍵Id了/// <summary>///獲取或設(shè)置是否禁用,邏輯上的刪除,非物理刪除/// </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>,然后定義該實(shí)體除主鍵以外的屬性和字段等即可,還是很簡(jiǎn)單的,也是很普通的寫法。
■?■■■■
復(fù)雜的實(shí)體模型
上邊寫了簡(jiǎn)單的方案,但是平時(shí)開發(fā)肯定不會(huì)是這樣的,不免會(huì)出現(xiàn)有關(guān)系的情況,也就是外鍵的問(wèn)題,比如用戶角色關(guān)系表UserRole,它里邊除了主鍵Id以外,肯定也會(huì)包含Uid和Rid,那如何設(shè)計(jì)呢,如果單純的繼承RootEntityTkey肯定是不行的,因?yàn)槿绻@么操作了,這個(gè)關(guān)系表中肯定就不能和User表或者Role表保持一致了,所以這三個(gè)字段都應(yīng)該設(shè)計(jì)成泛型的格式,那如何設(shè)計(jì)的?
我參照著實(shí)體泛型基類,又單獨(dú)針對(duì)特定的有外鍵需求的實(shí)體,抽離了一個(gè)中間父類,請(qǐng)注意我的命名:實(shí)體類-->父類(非必須)-->泛型基類,用UserRole來(lái)舉例。
1、還是先定義UserRole的實(shí)體類內(nèi)容
2、然后抽離父類,對(duì)外鍵和Pid等單純處理
3、最后將抽離的父類來(lái)繼承泛型基類
這樣不僅可以滿足這種外鍵的問(wèn)題,也可以來(lái)處理一些特殊的情況,比如Pid,你想一下,主鍵如果都泛型了,總不能Pid父id這種還是int吧,這里用接口表的抽離父類舉例:
BlogCore項(xiàng)目我已經(jīng)修改完成,最終的結(jié)果是這樣的:
核心的就是RootTkey這個(gè)文件夾下,就是這次修改的主要部分,其他的實(shí)體模型基本不用修改,只需要繼承特定的專屬父類/基類即可: RootEntityTkey<Tkey>。
■?■■■■
3、其他重要提醒
不要把抽離的父類生成到數(shù)據(jù)庫(kù)
在BlogCore項(xiàng)目中,我用的是自動(dòng)CodeFirst并可以生成種子數(shù)據(jù),當(dāng)生成表結(jié)構(gòu)的時(shí)候,我是根據(jù)命名空間來(lái)處理的,你在設(shè)計(jì)抽離的父類,比如UserRoleRoot<Tkey>的時(shí)候,注意修改命名空間,別生成到了數(shù)據(jù)庫(kù)里,當(dāng)然肯定也生成不進(jìn)去,會(huì)報(bào)錯(cuò)的,這里只是提個(gè)醒,因?yàn)槭荂odeFirst的邏輯是根據(jù)命名空間:
// 創(chuàng)建數(shù)據(jù)庫(kù)表,遍歷指定命名空間下的class, // 注意不要把其他命名空間下的也添加進(jìn)來(lái)。 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);} });當(dāng)然,你也可以自己優(yōu)化下,比如來(lái)個(gè)特性,或者繼承一個(gè)接口啥的來(lái)限制只有實(shí)體模型才可以生成到數(shù)據(jù)庫(kù)等等,看你的需要了。
■?■■■■
生成種子數(shù)據(jù)的時(shí)候,反序列化要注意
我也同時(shí)優(yōu)化了種子數(shù)據(jù)json的反序列化,比如整型用的是0,不是"0",這樣的問(wèn)題。
然后反序列化的方法也改用Newtonsoft.Json組件了,之前我之前自己寫的,在反序列化的時(shí)候有不識(shí)別null的問(wèn)題,所以需要配置一個(gè)setting來(lái)處理掉null,具體的代碼,可以查看DBSeed.cs 這個(gè)文件。這里舉一個(gè)例子:
JsonSerializerSettings setting = new JsonSerializerSettings();JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>{//日期類型默認(rèn)格式化處理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..."); }■?■■■■
項(xiàng)目如何初始化自定義主鍵類型
現(xiàn)在我的項(xiàng)目中,已經(jīng)完全配置好了int類型的模式了,如果你想使用Guid的話,應(yīng)該如何操作呢,很簡(jiǎn)單,只需要直接修改下泛型參數(shù)就行,這里用Advertisement舉例子說(shuō)明下:
1、修改泛型參數(shù)為Guid:
public class Advertisement : RootEntityTkey<Guid> {// 屬性字段等... }2、執(zhí)行Add操作
var ad = await _advertisementServices.Add(new Advertisement());3、注意倉(cāng)儲(chǔ)執(zhí)行方法
因?yàn)橹拔覀兌际鞘褂玫膇nt作為主鍵,然后用的.ExecuteReturnIdentityAsync()方法,這樣返回的是對(duì)應(yīng)的id。
但是現(xiàn)在用了Guid以后,就不能這么用了,因?yàn)檫@樣使用的話,這個(gè)方法是無(wú)效的.ExecuteReturnIdentityAsync(),不僅不會(huì)正常的返回id值,也無(wú)非自動(dòng)生成Guid的默認(rèn)值,你可以使用.ExecuteCommandAsync(),當(dāng)然可以直接使用.ExecuteReturnEntityAsync()這個(gè)方法,來(lái)返回實(shí)體,然后從實(shí)體里,獲取對(duì)應(yīng)的Id,這樣的話,不論是int還是Guid,都能返回出來(lái)了。
4、查看效果
設(shè)置了Guid以后,就可以看看效果了,上邊的0000-000-0000-000這樣的值,就是因?yàn)槭褂玫?ExecuteReturnIdentityAsync(),下邊的是正常的使用Command方法,或者直接ReturnEntity的方法。
總體來(lái)說(shuō)還是很方便的。
好啦,今天的分享暫時(shí)就是這樣的,希望能提供一個(gè)思路,無(wú)論是對(duì)BCVP項(xiàng)目,還是你自己的項(xiàng)目。
? end?
BCVP開發(fā)者社區(qū)推薦
歡迎你來(lái)
總結(jié)
以上是生活随笔為你收集整理的【BCVP升级】泛型主键的使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET Standard 来日苦短去日
- 下一篇: Dotnet Core使用特定的SDKR