[开源] .Net ORM FreeSql 1.10.0 稳步向前
寫在開頭
FreeSql 是 .NET 開源生態(tài)下的 ORM 輪子,轉(zhuǎn)眼快兩年了,說(shuō)真的開源不容易(只有經(jīng)歷過(guò)才明白)。今天帶點(diǎn)干貨和濕貨給大家,先說(shuō)下濕貨。
認(rèn)識(shí)我的人,知道 CSRedisCore 是我寫的另外一個(gè)開源組件,這個(gè)項(xiàng)目是 2016 年從 ctstone/csredis 項(xiàng)目 clone 到自己工作的項(xiàng)目中,修改源碼經(jīng)過(guò)一年多生產(chǎn)考驗(yàn),于 2017 年發(fā)布開源 https://github.com/2881099/csredis
ctstone/csredis 項(xiàng)目于 2014 年停止了更新,到我手里完善的功能如下:
連接池
哨兵高可用
集群
redis 2.8 以上的版本命令補(bǔ)充,包括 Geo、Stream
通訊協(xié)議 bug 修復(fù)
暫時(shí)想到的只有這些,之后可能再補(bǔ)充。FreeSql 文章標(biāo)題為什么要來(lái)說(shuō) csredis?
這兩年的時(shí)間里 95% 精力都用在了 FreeSql 上面, 5400+ 單元測(cè)試、支持十幾種數(shù)據(jù)庫(kù)適配,渣男辜負(fù)了 csredis 這個(gè)項(xiàng)目。最近一個(gè)多月開源圈子的奇葩事接二連三,居然有人跑去 ctstone/csredis 原作者的 issues 告我的狀,這個(gè)告狀的人還是 NOPI 原作者,因?yàn)楫?dāng)初他自己不維護(hù) NPOI .NET Core 版本了,社區(qū)有好人把 .NET Core 版本測(cè)試做好了開源(dotnetcore/NPOI),告狀的人很真心厲害,已經(jīng)成功把 nuget.org/dotnetcore.npoi 整下架了。
他并沒(méi)有得到滿足,之后開始針對(duì)整個(gè) NCC 社區(qū)成員,包括我。
他去了 sqlsugar issues 發(fā)表,說(shuō)要找出 FreeSql 抄襲 sqlsugar 的證據(jù)
他又去 fur issues 發(fā)表聲援,說(shuō)我黑他
他還去 csredis 原作者 issues 發(fā)布內(nèi)容,企圖告我的狀
并不是人人都像你一樣,強(qiáng)迫要求下游項(xiàng)目“歸檔”、“制裁”,試問(wèn) mysql 可以要求 mariadb 歸檔?針對(duì) NCC 組織還是針對(duì)我本人?CSRedisCore 并不在 NCC 開源組織下!!!
幾天月前我已經(jīng)開始了新的 redis .NET 開源組件庫(kù)的編寫,完全自主的看你能上哪里告狀。有了這么長(zhǎng)時(shí)間的 csredis 經(jīng)驗(yàn),重新寫一個(gè)能避免很多問(wèn)題,設(shè)計(jì)也會(huì)更好,后面我會(huì)花大部分時(shí)間做新項(xiàng)目,這便是今天帶來(lái)的濕貨,敬請(qǐng)期待發(fā)布!~!
入戲準(zhǔn)備
2018 年 12 月份開發(fā) FreeSql 到現(xiàn)在,2200 顆星,500 Issues,200K 包下載量。說(shuō)明還是有開發(fā)者關(guān)注和喜愛(ài),只要有人關(guān)注,就不會(huì)停更不修 BUG 一說(shuō)。大家有興趣可以看看更新記錄,看看我們的代碼提交量,5400+ 單元測(cè)試不說(shuō)非常多,個(gè)人覺(jué)得已經(jīng)超過(guò)很多國(guó)產(chǎn)項(xiàng)目。
23個(gè)月了,FreeSql 還活著,而且生命力頑強(qiáng)見下圖:
年底發(fā)布 2.0 版本正在收集需求中(歡迎前去 issues 誠(chéng)意登記),本文將介紹在過(guò)去的幾個(gè)月完成的一些有意義的功能介紹。
FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、XAUI、Blazor、以及還有說(shuō)不出來(lái)的運(yùn)行平臺(tái),因?yàn)榇a綠色無(wú)依賴,支持新平臺(tái)非常簡(jiǎn)單。目前單元測(cè)試數(shù)量:5400+,Nuget下載數(shù)量:200K+,源碼幾乎每天都有提交。值得高興的是 FreeSql 加入了 ncc 開源社區(qū):https://github.com/dotnetcore/FreeSql,加入組織之后社區(qū)責(zé)任感更大,需要更努力做好品質(zhì),為開源社區(qū)出一份力。
QQ群:4336577(已滿)、8578575(在線)、52508226(在線)
為什么要重復(fù)造輪子?
FreeSql 主要優(yōu)勢(shì)在于易用性上,基本是開箱即用,在不同數(shù)據(jù)庫(kù)之間切換兼容性比較好。作者花了大量的時(shí)間精力在這個(gè)項(xiàng)目,肯請(qǐng)您花半小時(shí)了解下項(xiàng)目,謝謝。
FreeSql 整體的功能特性如下:
支持 CodeFirst 對(duì)比結(jié)構(gòu)變化遷移;
支持 DbFirst 從數(shù)據(jù)庫(kù)導(dǎo)入實(shí)體類;
支持 豐富的表達(dá)式函數(shù),自定義解析;
支持 批量添加、批量更新、BulkCopy;
支持 導(dǎo)航屬性,貪婪加載、延時(shí)加載、級(jí)聯(lián)保存;
支持 讀寫分離、分表分庫(kù),租戶設(shè)計(jì);
支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/達(dá)夢(mèng)/神通/人大金倉(cāng)/MsAccess Ado.net 實(shí)現(xiàn)包,以及 Odbc 的專門實(shí)現(xiàn)包;
干貨來(lái)了
1.5.0 -> 1.10.0 更新的重要功能如下:
一、增加 Firebird 數(shù)據(jù)庫(kù)實(shí)現(xiàn);
二、增加 人大金倉(cāng)/神通 數(shù)據(jù)庫(kù)的訪問(wèn)支持;
三、增加 GlobalFilter.ApplyIf 創(chuàng)建動(dòng)態(tài)過(guò)濾器;
四、增加 ISelect.InsertInto 將查詢轉(zhuǎn)換為 INSERT INTO t1 SELECT ... FROM t2 執(zhí)行插入;
五、增加 IncludeMany(a => a.Childs).ToList(a => new { a.Childs }) 指定集合屬性返回;
六、增加 $"{a.Code}_{a.Id}" lambda 解析;
七、增加 lambda 表達(dá)式樹解析子查詢 ToList + string.Join() 產(chǎn)生 類似 group_concat 的效果;
八、增加 SqlExt 常用開窗函數(shù)的自定義表達(dá)式解析;
九、增加 ISelect/IInsert/IUpdate/IDelete CommandTimeout 方法設(shè)置命令超時(shí);
十、完善 WhereDynamicFilter 動(dòng)態(tài)過(guò)濾查詢;
十一、增加 BeginEdit/EndEdit 批量編輯數(shù)據(jù)的功能;
十二、增加 父子表(樹表)遞歸查詢、刪除功能;
FreeSql 使用非常簡(jiǎn)單,只需要定義一個(gè) IFreeSql 對(duì)象即可:
static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql,?connectionString).UseAutoSyncStructure(true)?//自動(dòng)同步實(shí)體結(jié)構(gòu)到數(shù)據(jù)庫(kù).Build();?//請(qǐng)務(wù)必定義成?Singleton?單例模式增加 Firebird 數(shù)據(jù)庫(kù)實(shí)現(xiàn);
它的體積比前輩Interbase縮小了幾十倍,但功能并無(wú)閹割。為了體現(xiàn)Firebird短小精悍的特色,開發(fā)小組在增加了超級(jí)服務(wù)器版本之后,又增加了嵌入版本,最新版本為2.0。Firebird的嵌入版有如下特色:
1、數(shù)據(jù)庫(kù)文件與Firebird網(wǎng)絡(luò)版本完全兼容,差別僅在于連接方式不同,可以實(shí)現(xiàn)零成本遷移。 2、數(shù)據(jù)庫(kù)文件僅受操作系統(tǒng)的限制,且支持將一個(gè)數(shù)據(jù)庫(kù)分割成不同文件,突破了操作系統(tǒng)最大文件的限制,提高了IO吞吐量。 3、完全支持SQL92標(biāo)準(zhǔn),支持大部分SQL-99標(biāo)準(zhǔn)功能。 4、豐富的開發(fā)工具支持,絕大部分基于Interbase的組件,可以直接使用于Firebird。 5、支持事務(wù)、存儲(chǔ)過(guò)程、觸發(fā)器等關(guān)系數(shù)據(jù)庫(kù)的所有特性。 6、可自己編寫擴(kuò)展函數(shù)(UDF)。 7、firebird其實(shí)并不是純粹的嵌入式數(shù)據(jù)庫(kù),embed版只是其眾多版本中的一個(gè)。不過(guò)做的也很小,把幾個(gè)dll加起來(lái)才不到5M,但是它支持絕大部份SQL92與SQL99標(biāo)準(zhǔn)
嵌入式,等于無(wú)需安裝的本地?cái)?shù)據(jù)庫(kù),歡迎體驗(yàn)!~~
增加 人大金倉(cāng)/神通 數(shù)據(jù)庫(kù)的訪問(wèn)支持
天津神舟通用數(shù)據(jù)技術(shù)有限公司(簡(jiǎn)稱“神舟通用公司”),隸屬于中國(guó)航天科技集團(tuán)(CASC)。是國(guó)內(nèi)從事數(shù)據(jù)庫(kù)、大數(shù)據(jù)解決方案和數(shù)據(jù)挖掘分析產(chǎn)品研發(fā)的專業(yè)公司。公司獲得了國(guó)家核高基科技重大專項(xiàng)重點(diǎn)支持,是核高基專項(xiàng)的牽頭承擔(dān)單位。自1993年在航天科技集團(tuán)開展數(shù)據(jù)庫(kù)研發(fā)以來(lái),神通數(shù)據(jù)庫(kù)已歷經(jīng)27年的發(fā)展歷程。公司核心產(chǎn)品主要包括神通關(guān)系型數(shù)據(jù)庫(kù)、神通KStore海量數(shù)據(jù)管理系統(tǒng)、神通商業(yè)智能套件等系列產(chǎn)品研發(fā)和市場(chǎng)銷售。基于產(chǎn)品組合,可形成支持交易處理、MPP數(shù)據(jù)庫(kù)集群、數(shù)據(jù)分析與處理等解決方案,可滿足多種應(yīng)用場(chǎng)景需求。產(chǎn)品通過(guò)了國(guó)家保密局涉密信息系統(tǒng)、公安部等保四級(jí)、軍B +級(jí)等安全評(píng)測(cè)和認(rèn)證。
北京人大金倉(cāng)信息技術(shù)股份有限公司(以下簡(jiǎn)稱“人大金倉(cāng)”)是具有自主知識(shí)產(chǎn)權(quán)的國(guó)產(chǎn)數(shù)據(jù)管理軟件與服務(wù)提供商。人大金倉(cāng)由中國(guó)人民大學(xué)一批最早在國(guó)內(nèi)開展數(shù)據(jù)庫(kù)教學(xué)、科研、開發(fā)的專家于1999年發(fā)起創(chuàng)立,先后承擔(dān)了國(guó)家“863”、“核高基”等重大專項(xiàng),研發(fā)出了具有國(guó)際先進(jìn)水平的大型通用數(shù)據(jù)庫(kù)產(chǎn)品。2018年,人大金倉(cāng)申報(bào)的“數(shù)據(jù)庫(kù)管理系統(tǒng)核心技術(shù)的創(chuàng)新與金倉(cāng)數(shù)據(jù)庫(kù)產(chǎn)業(yè)化”項(xiàng)目榮獲2018年度國(guó)家科學(xué)技術(shù)進(jìn)步二等獎(jiǎng),產(chǎn)學(xué)研的融合進(jìn)一步助力國(guó)家信息化建設(shè)。
隨著華為、中興事務(wù),國(guó)產(chǎn)數(shù)據(jù)庫(kù)市場(chǎng)相信是未來(lái)是趨勢(shì)走向,縱觀 .net core 整個(gè)圈子對(duì)國(guó)產(chǎn)神舟通用、人大金倉(cāng)數(shù)據(jù)庫(kù)的支持幾乎為 0,今天 FreeSql ORM 可以使用 CodeFirst/DbFirst 兩種模式進(jìn)行開發(fā)。
并且聲稱:FreeSql 對(duì)各數(shù)據(jù)庫(kù)沒(méi)有親兒子一說(shuō),除了 MsAcces 其他全部是親兒子,在功能提供方面一碗水端平。
眾所周知 EFCore for oracle 問(wèn)題多,并且現(xiàn)在才剛剛更新到 3.x,在這樣的背景下,一個(gè)國(guó)產(chǎn)數(shù)據(jù)庫(kù)更不能指望誰(shuí)實(shí)現(xiàn)好用的 EFCore。目前看來(lái)除了 EFCore for sqlserver 我們沒(méi)把握完全占優(yōu)勢(shì),起碼在其他數(shù)據(jù)庫(kù)肯定是我們更接地氣。
使用 FreeSql 訪問(wèn)人大金倉(cāng)/神通 數(shù)據(jù)庫(kù),只需要修改代碼如下即可:
static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.ShenTong,?connectionString)?//修改?DataType?設(shè)置切換數(shù)據(jù)庫(kù).UseAutoSyncStructure(true)?//自動(dòng)同步實(shí)體結(jié)構(gòu)到數(shù)據(jù)庫(kù).Build();?//請(qǐng)務(wù)必定義成?Singleton?單例模式增加 GlobalFilter.ApplyIf 創(chuàng)建動(dòng)態(tài)過(guò)濾器;
FreeSql 使用全局過(guò)濾器非常簡(jiǎn)單,我們的過(guò)濾器支持多表查詢、子查詢,只需要設(shè)置一次:
public?static?AsyncLocal<Guid>?TenantId?{?get;?set;?}?=?new?AsyncLocal<Guid>();fsql.GlobalFilter.Apply<ISoftDelete>("name1",?a?=>?a.IsDeleted?==?false).ApplyIf<ITenant>("tenant",?()?=>?TenantId.Value?!=?Guid.Empty,?a?=>?a.TenantId?==?TenantId.Value);上面增加了兩個(gè)過(guò)濾器,tenant 第二個(gè)參數(shù)正是增加的功能,當(dāng)委托條件成立時(shí)才會(huì)附加過(guò)濾器。
增加 ISelect.InsertInto 將查詢轉(zhuǎn)換為 INSERT INTO t1 SELECT ... FROM t2 執(zhí)行插入;
int?affrows?=?fsql.Select<Topic>().Limit(10).InsertInto(null,?a?=>?new?Topic2{Title?=?a.Title}); INSERT?INTO?`Topic2`(`Title`,?`Clicks`,?`CreateTime`) SELECT?a.`Title`,?0,?'0001-01-01?00:00:00'? FROM?`Topic`?a? limit?10注意:因?yàn)?Clicks、CreateTime 沒(méi)有被選擇,所以使用目標(biāo)實(shí)體屬性 [Column(InsertValueSql = xx)] 設(shè)置的值,或者使用目標(biāo)實(shí)體屬性的 c# 默認(rèn)值。
又一次完善了批量操作數(shù)據(jù)的功能,之前已經(jīng)有的功能如下:
fsql.InsertOrUpdate 相當(dāng)于 Merge Into/on duplicate key update
| MySql | on duplicate key update | 達(dá)夢(mèng) | merge into | |
| PostgreSQL | on conflict do update | 人大金倉(cāng) | on conflict do update | |
| SqlServer | merge into | 神通 | merge into | |
| Oracle | merge into | MsAccess | 不支持 | |
| Sqlite | replace into | |||
| Firebird | merge into |
fsql.Insert(數(shù)組).ExecuteAffrows() 相當(dāng)于批量插入
當(dāng)插入大批量數(shù)據(jù)時(shí),內(nèi)部采用分割分批執(zhí)行的邏輯進(jìn)行。分割規(guī)則:
| MySql | 5000 | 3000 |
| PostgreSQL | 5000 | 3000 |
| SqlServer | 1000 | 2100 |
| Oracle | 500 | 999 |
| Sqlite | 5000 | 999 |
fsql.Insert(數(shù)組).ExecuteSqlBulkCopy、ExecutePgCopy、ExecuteMySqlBulkCopy
fsql.Update<T>().SetSource(數(shù)組).ExecuteAffrows() 相當(dāng)于批量更新
增加 IncludeMany(a => a.Childs).ToList(a => new { a.Childs }) 指定集合屬性返回;
這個(gè)功能實(shí)在太重要了,在此之前 IncludeMany 和 ToList(指定字段) 八字不合,用起來(lái)有些麻煩。現(xiàn)在終于解決了!!~~
var?t111?=?fsql.Select<Topic>().IncludeMany(a?=>?a.TopicType.Photos).Where(a?=>?a.Id?<=?100).ToList(a?=>?new{a.Id,a.TopicType.Photos,Photos2?=?a.TopicType.Photos});增加 $"{a.Code}_{a.Id}" lambda 解析;
在之前查詢數(shù)據(jù)的時(shí)候,$"" 這種語(yǔ)法糖神器居然不能使用在 lambda 表達(dá)式中,實(shí)屬遺憾。現(xiàn)在終于可以了,如下:
var?item?=?fsql.GetRepository<Topic>().Insert(new?Topic?{?Clicks?=?101,?Title?=?"我是中國(guó)人101",?CreateTime?=?DateTime.Parse("2020-7-5")?}); var?sql?=?fsql.Select<Topic>().WhereDynamic(item).ToSql(a?=>?new {str?=?$"x{a.Id?+?1}z-{a.CreateTime.ToString("yyyyMM")}{a.Title}{a.Title}" }); Assert.Equal($@"SELECT?concat('x',ifnull((a.`Id`?+?1),?''),'z-',ifnull(date_format(a.`CreateTime`,'%Y%m'),?''),'',ifnull(a.`Title`,?''),'',ifnull(a.`Title`,?''),'')?as1? FROM?`tb_topic`?a? WHERE?(a.`Id`?=?{item.Id})",?sql);再次說(shuō)明:都是親兒子,并且都有對(duì)應(yīng)的單元測(cè)試,兄臺(tái)大可放心用在不同的數(shù)據(jù)庫(kù)中
增加 lambda 表達(dá)式樹解析子查詢 ToList + string.Join() 產(chǎn)生 類似 group_concat 的效果;
v1.8.0+ string.Join + ToList 實(shí)現(xiàn)將子查詢的多行結(jié)果,拼接為一個(gè)字符串,如:"1,2,3,4"
fsql.Select<Topic>().ToList(a?=>?new?{id?=?a.Id,concat?=?string.Join(",",?fsql.Select<StringJoin01>().ToList(b?=>?b.Id)) }); //SELECT?a.`Id`,?(SELECT?group_concat(b.`Id`?separator?',')? //????FROM?`StringJoin01`?b)? //FROM?`Topic`?a該語(yǔ)法,在不同數(shù)據(jù)庫(kù)都作了相應(yīng)的 SQL 翻譯。
增加 SqlExt 常用的自定義表達(dá)式樹解析;
SqlExt.cs 定義了一些常用的表達(dá)式樹解析,如下:
fsql.Select<T1,?T2>().InnerJoin((a,?b)?=>?b.Id?==?a.Id).ToList((a,?b)?=>?new{Id?=?a.Id,EdiId?=?b.Id,over1?=?SqlExt.Rank().Over().OrderBy(a.Id).OrderByDescending(b.EdiId).ToValue(),case1?=?SqlExt.Case().When(a.Id?==?1,?10).When(a.Id?==?2,?11).When(a.Id?==?3,?12).When(a.Id?==?4,?13).When(a.Id?==?5,?SqlExt.Case().When(b.Id?==?1,?10000).Else(999).End()).End(),?//這里因?yàn)閺?fù)雜才這樣,一般使用三元表達(dá)式即可:a.Id?==?1???10?:?11groupct1?=?SqlExt.GroupConcat(a.Id).Distinct().OrderBy(b.EdiId).Separator("_").ToValue()});本功能利用 FreeSql 自定義解析實(shí)現(xiàn)常用表達(dá)式樹解析,歡迎 PR 補(bǔ)充
增加 ISelect/IInsert/IUpdate/IDelete CommandTimeout 方法設(shè)置命令超時(shí);
現(xiàn)在每條 crud 都可以設(shè)置命令執(zhí)行的超時(shí)值,如下:
fsql.Insert<items).CommandTimeout(60).ExecuteAffrows();fsql.Delete<T>().Where(...).CommandTimeout(60).ExecuteAffrows();fsql.Update<T>().Set(a?=>?a.Clicks?+?1).Where(...).CommandTimeout(60).ExecuteAffrows();fsql.Select<T>().Where(...).CommandTimeout(60).ToList();完善 WhereDynamicFilter 動(dòng)態(tài)過(guò)濾查詢
是否見過(guò)這樣的高級(jí)查詢功能,WhereDynamicFilter 在后端可以輕松完成這件事情,前端根據(jù) UI 組裝好對(duì)應(yīng)的 json 字符串傳給后端就行,如下:
DynamicFilterInfo?dyfilter?=?JsonConvert.DeserializeObject<DynamicFilterInfo>(@" {""Logic""?:?""Or"",""Filters""?:[{""Field""?:?""Code"",?""Operator""?:?""NotContains"",?""Value""?:?""val1"",?""Filters""?:?[{?""Field""?:?""Name"",?""Operator""?:?""NotStartsWith"",?""Value""?:?""val2""?}]},{""Field""?:?""Parent.Code"",?""Operator""?:?""Equals"",?""Value""?:?""val11"",""Filters""?:?[{?""Field""?:?""Parent.Name"",?""Operator""?:?""Contains"",?""Value""?:?""val22""?}]}] }"); fsql.Select<VM_District_Parent>().WhereDynamicFilter(dyfilter).ToList(); //SELECT?a.""Code"",?a.""Name"",?a.""ParentCode"",?a__Parent.""Code""?as4,?a__Parent.""Name""?as5,?a__Parent.""ParentCode""?as6? //FROM?""D_District""?a? //LEFT?JOIN?""D_District""?a__Parent?ON?a__Parent.""Code""?=?a.""ParentCode""? //WHERE?(not((a.""Code"")?LIKE?'%val1%')?AND?not((a.""Name"")?LIKE?'val2%')?OR?a__Parent.""Code""?=?'val11'?AND?(a__Parent.""Name"")?LIKE?'%val22%')ISelect.WhereDynamicFilter 方法實(shí)現(xiàn)動(dòng)態(tài)過(guò)濾條件(與前端交互),支持的操作符:
Contains/StartsWith/EndsWith/NotContains/NotStartsWith/NotEndsWith:包含/不包含,like '%xx%',或者 like 'xx%',或者 like '%xx'
Equal/NotEqual:等于/不等于
GreaterThan/GreaterThanOrEqual:大于/大于等于
LessThan/LessThanOrEqual:小于/小于等于
Range:范圍查詢
DateRange:日期范圍,有特殊處理 value[1] + 1
Any/NotAny:是否符合 value 中任何一項(xiàng)(直白的說(shuō)是 SQL IN)
增加 BeginEdit/EndEdit 批量編輯數(shù)據(jù)的功能;
場(chǎng)景:winform 加載表數(shù)據(jù)后,一頓添加、修改、刪除操作之后,點(diǎn)擊【保存】
[Fact] public?void?BeginEdit() {fsql.Delete<BeginEdit01>().Where("1=1").ExecuteAffrows();var?repo?=?fsql.GetRepository<BeginEdit01>();var?cts?=?new[]?{new?BeginEdit01?{?Name?=?"分類1"?},new?BeginEdit01?{?Name?=?"分類1_1"?},new?BeginEdit01?{?Name?=?"分類1_2"?},new?BeginEdit01?{?Name?=?"分類1_3"?},new?BeginEdit01?{?Name?=?"分類2"?},new?BeginEdit01?{?Name?=?"分類2_1"?},new?BeginEdit01?{?Name?=?"分類2_2"?}}.ToList();repo.Insert(cts);repo.BeginEdit(cts);?//開始對(duì)?cts?進(jìn)行編輯cts.Add(new?BeginEdit01?{?Name?=?"分類2_3"?});cts[0].Name?=?"123123";cts.RemoveAt(1);Assert.Equal(3,?repo.EndEdit()); } class?BeginEdit01 {public?Guid?Id?{?get;?set;?}public?string?Name?{?get;?set;?} }上面的代碼 EndEdit 方法執(zhí)行的時(shí)候產(chǎn)生 3 條 SQL 如下:
INSERT?INTO?"BeginEdit01"("Id",?"Name")?VALUES('5f26bf07-6ac3-cbe8-00da-7dd74818c3a6',?'分類2_3')UPDATE?"BeginEdit01"?SET?"Name"?=?'123123'? WHERE?("Id"?=?'5f26bf00-6ac3-cbe8-00da-7dd01be76e26')DELETE?FROM?"BeginEdit01"?WHERE?("Id"?=?'5f26bf00-6ac3-cbe8-00da-7dd11bcf54dc')提醒:該操作只對(duì)變量 cts 有效,不是針對(duì)全表對(duì)比更新。
增加 父子表(樹表)遞歸查詢、刪除功能;
無(wú)限級(jí)分類(父子)是一種比較常用的表設(shè)計(jì),每種設(shè)計(jì)方式突出優(yōu)勢(shì)的同時(shí)也帶來(lái)缺陷,如:
方法1:表設(shè)計(jì)中只有 parent_id 字段,困擾:查詢麻煩(本文可解決);
方法2:表設(shè)計(jì)中冗余子級(jí)id便于查詢,困擾:添加/更新/刪除的時(shí)候需要重新計(jì)算;
方法3:表設(shè)計(jì)中存儲(chǔ)左右值編碼,困擾:同上;
方法1設(shè)計(jì)最簡(jiǎn)單,我們正是解決它設(shè)計(jì)簡(jiǎn)單,使用復(fù)雜的問(wèn)題。
首先,按照導(dǎo)航屬性的定義,定義好父子屬性:
public?class?Area {[Column(IsPrimary?=?true)]public?string?Code?{?get;?set;?}public?string?Name?{?get;?set;?}public?virtual?string?ParentCode?{?get;?set;?}[Navigate(nameof(ParentCode))]public?Area?Parent?{?get;?set;?}[Navigate(nameof(ParentCode))]public?List<Area>?Childs?{?get;?set;?} }定義 Parent 屬性,在表達(dá)式中可以這樣:
fsql.Select<Area>().Where(a?=>?a.Parent.Parent.Parent.Name?==?"中國(guó)").First();定義 Childs 屬性,在表達(dá)式中可以這樣(子查詢):
fsql.Select<Area>().Where(a?=>?a.Childs.AsSelect().Any(c?=>?c.Name?==?"北京")).First();定義 Childs 屬性,還可以使用【級(jí)聯(lián)保存】、【貪婪加載】 等等操作。
利用級(jí)聯(lián)保存,添加測(cè)試數(shù)據(jù)如下:
fsql.Delete<Area>().Where("1=1").ExecuteAffrows(); var?repo?=?fsql.GetRepository<Area>(); repo.DbContextOptions.EnableAddOrUpdateNavigateList?=?true; repo.DbContextOptions.NoneParameter?=?true; repo.Insert(new?Area {Code?=?"100000",Name?=?"中國(guó)",Childs?=?new?List<Area>(new[]?{new?Area{Code?=?"110000",Name?=?"北京",Childs?=?new?List<Area>(new[]?{new?Area{?Code="110100",?Name?=?"北京市"?},new?Area{?Code="110101",?Name?=?"東城區(qū)"?},})}}) });功能1:ToTreeList
配置好父子屬性之后,就可以這樣用了:
var?t1?=?fsql.Select<Area>().ToTreeList(); Assert.Single(t1); Assert.Equal("100000",?t1[0].Code); Assert.Single(t1[0].Childs); Assert.Equal("110000",?t1[0].Childs[0].Code); Assert.Equal(2,?t1[0].Childs[0].Childs.Count); Assert.Equal("110100",?t1[0].Childs[0].Childs[0].Code); Assert.Equal("110101",?t1[0].Childs[0].Childs[1].Code);查詢數(shù)據(jù)本來(lái)是平面的,ToTreeList 方法將返回的平面數(shù)據(jù)在內(nèi)存中加工為樹型 List 返回。
功能2:AsTreeCte 遞歸刪除
很常見的無(wú)限級(jí)分類表功能,刪除樹節(jié)點(diǎn)時(shí),把子節(jié)點(diǎn)也處理一下。
fsql.Select<Area>().Where(a?=>?a.Name?==?"中國(guó)").AsTreeCte().ToDelete().ExecuteAffrows();?//刪除?中國(guó)?下的所有記錄如果軟刪除:
fsql.Select<Area>().Where(a?=>?a.Name?==?"中國(guó)").AsTreeCte().ToUpdate().Set(a?=>?a.IsDeleted,?true).ExecuteAffrows();?//軟刪除?中國(guó)?下的所有記錄功能3:AsTreeCte 遞歸查詢
若不做數(shù)據(jù)冗余的無(wú)限級(jí)分類表設(shè)計(jì),遞歸查詢少不了,AsTreeCte 正是解決遞歸查詢的封裝,方法參數(shù)說(shuō)明:
| (可選) pathSelector | 路徑內(nèi)容選擇,可以設(shè)置查詢返回:中國(guó) -> 北京 -> 東城區(qū) |
| (可選) up | false(默認(rèn)):由父級(jí)向子級(jí)的遞歸查詢,true:由子級(jí)向父級(jí)的遞歸查詢 |
| (可選) pathSeparator | 設(shè)置 pathSelector 的連接符,默認(rèn):-> |
| (可選) level | 設(shè)置遞歸層級(jí) |
通過(guò)測(cè)試的數(shù)據(jù)庫(kù):MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、達(dá)夢(mèng)、人大金倉(cāng)
姿勢(shì)一:AsTreeCte() + ToTreeList
var?t2?=?fsql.Select<Area>().Where(a?=>?a.Name?==?"中國(guó)").AsTreeCte()?//查詢?中國(guó)?下的所有記錄.OrderBy(a?=>?a.Code).ToTreeList();?//非必須,也可以使用?ToList(見姿勢(shì)二) Assert.Single(t2); Assert.Equal("100000",?t2[0].Code); Assert.Single(t2[0].Childs); Assert.Equal("110000",?t2[0].Childs[0].Code); Assert.Equal(2,?t2[0].Childs[0].Childs.Count); Assert.Equal("110100",?t2[0].Childs[0].Childs[0].Code); Assert.Equal("110101",?t2[0].Childs[0].Childs[1].Code); //?WITH?"as_tree_cte" //?as //?( //?SELECT?0?as?cte_level,?a."Code",?a."Name",?a."ParentCode"? //?FROM?"Area"?a? //?WHERE?(a."Name"?=?'中國(guó)')//?union?all//?SELECT?wct1.cte_level?+?1?as?cte_level,?wct2."Code",?wct2."Name",?wct2."ParentCode"? //?FROM?"as_tree_cte"?wct1? //?INNER?JOIN?"Area"?wct2?ON?wct2."ParentCode"?=?wct1."Code" //?) //?SELECT?a."Code",?a."Name",?a."ParentCode"? //?FROM?"as_tree_cte"?a? //?ORDER?BY?a."Code"姿勢(shì)二:AsTreeCte() + ToList
var?t3?=?fsql.Select<Area>().Where(a?=>?a.Name?==?"中國(guó)").AsTreeCte().OrderBy(a?=>?a.Code).ToList(); Assert.Equal(4,?t3.Count); Assert.Equal("100000",?t3[0].Code); Assert.Equal("110000",?t3[1].Code); Assert.Equal("110100",?t3[2].Code); Assert.Equal("110101",?t3[3].Code); //執(zhí)行的?SQL?與姿勢(shì)一相同姿勢(shì)三:AsTreeCte(pathSelector) + ToList
設(shè)置 pathSelector 參數(shù)后,如何返回隱藏字段?
var?t4?=?fsql.Select<Area>().Where(a?=>?a.Name?==?"中國(guó)").AsTreeCte(a?=>?a.Name?+?"["?+?a.Code?+?"]").OrderBy(a?=>?a.Code).ToList(a?=>?new?{?item?=?a,?level?=?Convert.ToInt32("a.cte_level"),?path?=?"a.cte_path"?}); Assert.Equal(4,?t4.Count); Assert.Equal("100000",?t4[0].item.Code); Assert.Equal("110000",?t4[1].item.Code); Assert.Equal("110100",?t4[2].item.Code); Assert.Equal("110101",?t4[3].item.Code); Assert.Equal("中國(guó)[100000]",?t4[0].path); Assert.Equal("中國(guó)[100000]?->?北京[110000]",?t4[1].path); Assert.Equal("中國(guó)[100000]?->?北京[110000]?->?北京市[110100]",?t4[2].path); Assert.Equal("中國(guó)[100000]?->?北京[110000]?->?東城區(qū)[110101]",?t4[3].path); //?WITH?"as_tree_cte" //?as //?( //?SELECT?0?as?cte_level,?a."Name"?||?'['?||?a."Code"?||?']'?as?cte_path,?a."Code",?a."Name",?a."ParentCode"? //?FROM?"Area"?a? //?WHERE?(a."Name"?=?'中國(guó)')//?union?all//?SELECT?wct1.cte_level?+?1?as?cte_level,?wct1.cte_path?||?'?->?'?||?wct2."Name"?||?'['?||?wct2."Code"?||?']'?as?cte_path,?wct2."Code",?wct2."Name",?wct2."ParentCode"? //?FROM?"as_tree_cte"?wct1? //?INNER?JOIN?"Area"?wct2?ON?wct2."ParentCode"?=?wct1."Code" //?) //?SELECT?a."Code"?as1,?a."Name"?as2,?a."ParentCode"?as5,?a.cte_level?as6,?a.cte_path?as7? //?FROM?"as_tree_cte"?a? //?ORDER?BY?a."Code"更多姿勢(shì)...請(qǐng)根據(jù)代碼注釋進(jìn)行嘗試
寫在最后
給 .NET 開源社區(qū)貢獻(xiàn)一點(diǎn)力時(shí),希望作者的努力能打動(dòng)到你,請(qǐng)求正在使用的、善良的您能動(dòng)一動(dòng)小手指,把文章轉(zhuǎn)發(fā)一下,讓更多人知道 .NET 有這樣一個(gè)好用的 ORM 存在。謝謝了!!
FreeSql 使用最寬松的開源協(xié)議 MIT https://github.com/dotnetcore/FreeSql,完全可以商用,文檔齊全。QQ群:4336577(已滿)、8578575(在線)、52508226(在線)
如果你有好的 ORM 實(shí)現(xiàn)想法,歡迎給作者留言討論,謝謝觀看!
2.0 版本意見正在登記中:https://github.com/dotnetcore/FreeSql/issues/469
本文作者:FreeSql & CSRedis
本文鏈接:https://www.cnblogs.com/kellynic/p/13855784.html
總結(jié)
以上是生活随笔為你收集整理的[开源] .Net ORM FreeSql 1.10.0 稳步向前的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: asp.net core监控—引入Pro
- 下一篇: CAP-微服务间通信实践