寫在開頭
FreeSql 是 .NET 開源生態(tài)下的 ORM 輪子,轉(zhuǎn)眼快兩年了雳旅,說真的開源不容易(只有經(jīng)歷過才明白)。今天帶點(diǎn)干貨和濕貨給大家间聊,先說下濕貨攒盈。
認(rèn)識(shí)我的人,知道 CSRedisCore 是我寫的另外一個(gè)開源組件哎榴,這個(gè)項(xiàng)目是 2016 年從 ctstone/csredis 項(xiàng)目 clone 到自己工作的項(xiàng)目中型豁,修改源碼經(jīng)過一年多生產(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)題為什么要來說 csredis?
這兩年的時(shí)間里 95% 精力都用在了 FreeSql 上面姿鸿, 5400+ 單元測(cè)試谆吴、支持十幾種數(shù)據(jù)庫適配,渣男辜負(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 整下架了。
他并沒有得到滿足旺隙,之后開始針對(duì)整個(gè) NCC 社區(qū)成員绒极,包括我。
- 他去了 sqlsugar issues 發(fā)表蔬捷,說要找出 FreeSql 抄襲 sqlsugar 的證據(jù)
- 他又去 fur issues 發(fā)表聲援垄提,說我黑他
- 他還去 csredis 原作者 issues 發(fā)布內(nèi)容榔袋,企圖告我的狀
并不是人人都像你一樣,強(qiáng)迫要求下游項(xiàng)目“歸檔”铡俐、“制裁”凰兑,試問 mysql 可以要求 mariadb 歸檔?針對(duì) NCC 組織還是針對(duì)我本人审丘?CSRedisCore 并不在 NCC 開源組織下@艄弧!滩报!
幾天月前我已經(jīng)開始了新的 redis .NET 開源組件庫的編寫锅知,完全自主的看你能上哪里告狀。有了這么長(zhǎng)時(shí)間的 csredis 經(jīng)驗(yàn)脓钾,重新寫一個(gè)能避免很多問題售睹,設(shè)計(jì)也會(huì)更好,后面我會(huì)花大部分時(shí)間做新項(xiàng)目可训,這便是今天帶來的濕貨昌妹,敬請(qǐng)期待發(fā)布!~握截!
入戲準(zhǔn)備
2018 年 12 月份開發(fā) FreeSql 到現(xiàn)在捺宗,2200 顆星,500 Issues川蒙,200K 包下載量蚜厉。說明還是有開發(fā)者關(guān)注和喜愛,只要有人關(guān)注畜眨,就不會(huì)停更不修 BUG 一說昼牛。大家有興趣可以看看更新記錄,看看我們的代碼提交量康聂,5400+ 單元測(cè)試不說非常多贰健,個(gè)人覺得已經(jīng)超過很多國產(chǎn)項(xiàng)目船庇。
23個(gè)月了勇哗,F(xiàn)reeSql 還活著,而且生命力頑強(qiáng)見下圖:
年底發(fā)布 2.0 版本正在收集需求中(歡迎前去 issues 誠意登記)菊霜,本文將介紹在過去的幾個(gè)月完成的一些有意義的功能介紹氓侧。
FreeSql 是 .Net ORM脊另,能支持 .NetFramework4.0+、.NetCore约巷、Xamarin偎痛、XAUI、Blazor独郎、以及還有說不出來的運(yùn)行平臺(tái)踩麦,因?yàn)榇a綠色無依賴枚赡,支持新平臺(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ù)庫之間切換兼容性比較好竣贪。作者花了大量的時(shí)間精力在這個(gè)項(xiàng)目军洼,肯請(qǐng)您花半小時(shí)了解下項(xiàng)目,謝謝演怎。
FreeSql 整體的功能特性如下:
- 支持 CodeFirst 對(duì)比結(jié)構(gòu)變化遷移匕争;
- 支持 DbFirst 從數(shù)據(jù)庫導(dǎo)入實(shí)體類;
- 支持 豐富的表達(dá)式函數(shù)爷耀,自定義解析甘桑;
- 支持 批量添加、批量更新歹叮、BulkCopy跑杭;
- 支持 導(dǎo)航屬性,貪婪加載咆耿、延時(shí)加載德谅、級(jí)聯(lián)保存;
- 支持 讀寫分離萨螺、分表分庫窄做,租戶設(shè)計(jì);
- 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/達(dá)夢(mèng)/神通/人大金倉/MsAccess Ado.net 實(shí)現(xiàn)包慰技,以及 Odbc 的專門實(shí)現(xiàn)包椭盏;
干貨來了
1.5.0 -> 1.10.0 更新的重要功能如下:
一、增加 Firebird 數(shù)據(jù)庫實(shí)現(xiàn)惹盼;
二庸汗、增加 人大金倉/神通 數(shù)據(jù)庫的訪問支持惫确;
三手报、增加 GlobalFilter.ApplyIf 創(chuàng)建動(dòng)態(tài)過濾器蚯舱;
四、增加 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)過濾查詢蛔外;
十一蛆楞、增加 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ù)庫
.Build(); //請(qǐng)務(wù)必定義成 Singleton 單例模式
增加 Firebird 數(shù)據(jù)庫實(shí)現(xiàn)臂聋;
它的體積比前輩Interbase縮小了幾十倍,但功能并無閹割或南。為了體現(xiàn)Firebird短小精悍的特色逻住,開發(fā)小組在增加了超級(jí)服務(wù)器版本之后,又增加了嵌入版本迎献,最新版本為2.0瞎访。Firebird的嵌入版有如下特色:
1、數(shù)據(jù)庫文件與Firebird網(wǎng)絡(luò)版本完全兼容吁恍,差別僅在于連接方式不同扒秸,可以實(shí)現(xiàn)零成本遷移。
2冀瓦、數(shù)據(jù)庫文件僅受操作系統(tǒng)的限制伴奥,且支持將一個(gè)數(shù)據(jù)庫分割成不同文件,突破了操作系統(tǒng)最大文件的限制翼闽,提高了IO吞吐量拾徙。
3、完全支持SQL92標(biāo)準(zhǔn)感局,支持大部分SQL-99標(biāo)準(zhǔn)功能尼啡。
4暂衡、豐富的開發(fā)工具支持,絕大部分基于Interbase的組件崖瞭,可以直接使用于Firebird狂巢。
5、支持事務(wù)书聚、存儲(chǔ)過程唧领、觸發(fā)器等關(guān)系數(shù)據(jù)庫的所有特性。
6雌续、可自己編寫擴(kuò)展函數(shù)(UDF)斩个。
7、firebird其實(shí)并不是純粹的嵌入式數(shù)據(jù)庫驯杜,embed版只是其眾多版本中的一個(gè)萨驶。不過做的也很小,把幾個(gè)dll加起來才不到5M艇肴,但是它支持絕大部份SQL92與SQL99標(biāo)準(zhǔn)
嵌入式腔呜,等于無需安裝的本地?cái)?shù)據(jù)庫,歡迎體驗(yàn)再悼!~~
增加 人大金倉/神通 數(shù)據(jù)庫的訪問支持
天津神舟通用數(shù)據(jù)技術(shù)有限公司(簡(jiǎn)稱“神舟通用公司”)核畴,隸屬于中國航天科技集團(tuán)(CASC)。是國內(nèi)從事數(shù)據(jù)庫冲九、大數(shù)據(jù)解決方案和數(shù)據(jù)挖掘分析產(chǎn)品研發(fā)的專業(yè)公司谤草。公司獲得了國家核高基科技重大專項(xiàng)重點(diǎn)支持,是核高基專項(xiàng)的牽頭承擔(dān)單位莺奸。自1993年在航天科技集團(tuán)開展數(shù)據(jù)庫研發(fā)以來丑孩,神通數(shù)據(jù)庫已歷經(jīng)27年的發(fā)展歷程。公司核心產(chǎn)品主要包括神通關(guān)系型數(shù)據(jù)庫灭贷、神通KStore海量數(shù)據(jù)管理系統(tǒng)温学、神通商業(yè)智能套件等系列產(chǎn)品研發(fā)和市場(chǎng)銷售∩跖保基于產(chǎn)品組合仗岖,可形成支持交易處理、MPP數(shù)據(jù)庫集群览妖、數(shù)據(jù)分析與處理等解決方案轧拄,可滿足多種應(yīng)用場(chǎng)景需求。產(chǎn)品通過了國家保密局涉密信息系統(tǒng)讽膏、公安部等保四級(jí)檩电、軍B +級(jí)等安全評(píng)測(cè)和認(rèn)證。
北京人大金倉信息技術(shù)股份有限公司(以下簡(jiǎn)稱“人大金倉”)是具有自主知識(shí)產(chǎn)權(quán)的國產(chǎn)數(shù)據(jù)管理軟件與服務(wù)提供商。人大金倉由中國人民大學(xué)一批最早在國內(nèi)開展數(shù)據(jù)庫教學(xué)俐末、科研料按、開發(fā)的專家于1999年發(fā)起創(chuàng)立,先后承擔(dān)了國家“863”鹅搪、“核高基”等重大專項(xiàng)站绪,研發(fā)出了具有國際先進(jìn)水平的大型通用數(shù)據(jù)庫產(chǎn)品遭铺。2018年丽柿,人大金倉申報(bào)的“數(shù)據(jù)庫管理系統(tǒng)核心技術(shù)的創(chuàng)新與金倉數(shù)據(jù)庫產(chǎn)業(yè)化”項(xiàng)目榮獲2018年度國家科學(xué)技術(shù)進(jìn)步二等獎(jiǎng),產(chǎn)學(xué)研的融合進(jìn)一步助力國家信息化建設(shè)魂挂。
隨著華為甫题、中興事務(wù),國產(chǎn)數(shù)據(jù)庫市場(chǎng)相信是未來是趨勢(shì)走向涂召,縱觀 .net core 整個(gè)圈子對(duì)國產(chǎn)神舟通用坠非、人大金倉數(shù)據(jù)庫的支持幾乎為 0,今天 FreeSql ORM 可以使用 CodeFirst/DbFirst 兩種模式進(jìn)行開發(fā)果正。
并且聲稱:FreeSql 對(duì)各數(shù)據(jù)庫沒有親兒子一說炎码,除了 MsAcces 其他全部是親兒子,在功能提供方面一碗水端平秋泳。
眾所周知 EFCore for oracle 問題多潦闲,并且現(xiàn)在才剛剛更新到 3.x,在這樣的背景下迫皱,一個(gè)國產(chǎn)數(shù)據(jù)庫更不能指望誰實(shí)現(xiàn)好用的 EFCore歉闰。目前看來除了 EFCore for sqlserver 我們沒把握完全占優(yōu)勢(shì),起碼在其他數(shù)據(jù)庫肯定是我們更接地氣卓起。
使用 FreeSql 訪問人大金倉/神通 數(shù)據(jù)庫和敬,只需要修改代碼如下即可:
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.ShenTong, connectionString) //修改 DataType 設(shè)置切換數(shù)據(jù)庫
.UseAutoSyncStructure(true) //自動(dòng)同步實(shí)體結(jié)構(gòu)到數(shù)據(jù)庫
.Build(); //請(qǐng)務(wù)必定義成 Singleton 單例模式
增加 GlobalFilter.ApplyIf 創(chuàng)建動(dòng)態(tài)過濾器;
FreeSql 使用全局過濾器非常簡(jiǎn)單戏阅,我們的過濾器支持多表查詢昼弟、子查詢,只需要設(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è)過濾器奕筐,tenant 第二個(gè)參數(shù)正是增加的功能私杜,當(dāng)委托條件成立時(shí)才會(huì)附加過濾器。
增加 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 沒有被選擇,所以使用目標(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
Database | Features | Database | Features | |
---|---|---|---|---|
MySql | on duplicate key update | 達(dá)夢(mèng) | merge into | |
PostgreSQL | on conflict do update | 人大金倉 | 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)于批量插入
var t2 = fsql.Insert(items).ExecuteAffrows();
//INSERT INTO `Topic`(`Clicks`, `Title`, `CreateTime`)
//VALUES(?Clicks0, ?Title0, ?CreateTime0), (?Clicks1, ?Title1, ?CreateTime1),
//(?Clicks2, ?Title2, ?CreateTime2), (?Clicks3, ?Title3, ?CreateTime3),
//(?Clicks4, ?Title4, ?CreateTime4), (?Clicks5, ?Title5, ?CreateTime5),
//(?Clicks6, ?Title6, ?CreateTime6), (?Clicks7, ?Title7, ?CreateTime7),
//(?Clicks8, ?Title8, ?CreateTime8), (?Clicks9, ?Title9, ?CreateTime9)
當(dāng)插入大批量數(shù)據(jù)時(shí),內(nèi)部采用分割分批執(zhí)行的邏輯進(jìn)行瓢捉。分割規(guī)則:
數(shù)量 | 參數(shù)量 | |
---|---|---|
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(指定字段) 八字不合,用起來有些麻煩∧诚遥現(xiàn)在終于解決了M┨馈!~~
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í)候怔毛,$"" 這種語法糖神器居然不能使用在 lambda 表達(dá)式中,實(shí)屬遺憾√诮担現(xiàn)在終于可以了拣度,如下:
var item = fsql.GetRepository<Topic>().Insert(new Topic { Clicks = 101, Title = "我是中國人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);
再次說明:都是親兒子,并且都有對(duì)應(yīng)的單元測(cè)試螃壤,兄臺(tái)大可放心用在不同的數(shù)據(jù)庫中
增加 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
該語法冤馏,在不同數(shù)據(jù)庫都作了相應(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 : 11
groupct1 = 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)過濾查詢
是否見過這樣的高級(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)過濾條件(與前端交互)蚣旱,支持的操作符:
- 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)(直白的說是 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ì)比更新延都。
增加 父子表(樹表)遞歸查詢、刪除功能睛竣;
無限級(jí)分類(父子)是一種比較常用的表設(shè)計(jì)晰房,每種設(shè)計(jì)方式突出優(yōu)勢(shì)的同時(shí)也帶來缺陷,如:
- 方法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ù)雜的問題簸搞。
首先扁位,按照導(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 == "中國").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 = "中國",
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ù)本來是平面的垦细,ToTreeList 方法將返回的平面數(shù)據(jù)在內(nèi)存中加工為樹型 List 返回。
功能2:AsTreeCte 遞歸刪除
很常見的無限級(jí)分類表功能挡逼,刪除樹節(jié)點(diǎn)時(shí)括改,把子節(jié)點(diǎn)也處理一下。
fsql.Select<Area>()
.Where(a => a.Name == "中國")
.AsTreeCte()
.ToDelete()
.ExecuteAffrows(); //刪除 中國 下的所有記錄
如果軟刪除:
fsql.Select<Area>()
.Where(a => a.Name == "中國")
.AsTreeCte()
.ToUpdate()
.Set(a => a.IsDeleted, true)
.ExecuteAffrows(); //軟刪除 中國 下的所有記錄
功能3:AsTreeCte 遞歸查詢
若不做數(shù)據(jù)冗余的無限級(jí)分類表設(shè)計(jì)家坎,遞歸查詢少不了嘱能,AsTreeCte 正是解決遞歸查詢的封裝,方法參數(shù)說明:
參數(shù) | 描述 |
---|---|
(可選) pathSelector | 路徑內(nèi)容選擇虱疏,可以設(shè)置查詢返回:中國 -> 北京 -> 東城區(qū) |
(可選) up | false(默認(rèn)):由父級(jí)向子級(jí)的遞歸查詢惹骂,true:由子級(jí)向父級(jí)的遞歸查詢 |
(可選) pathSeparator | 設(shè)置 pathSelector 的連接符,默認(rèn):-> |
(可選) level | 設(shè)置遞歸層級(jí) |
通過測(cè)試的數(shù)據(jù)庫:MySql8.0做瞪、SqlServer对粪、PostgreSQL、Oracle、Sqlite衩侥、達(dá)夢(mèng)国旷、人大金倉
姿勢(shì)一:AsTreeCte() + ToTreeList
var t2 = fsql.Select<Area>()
.Where(a => a.Name == "中國")
.AsTreeCte() //查詢 中國 下的所有記錄
.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" = '中國')
// 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 == "中國")
.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 == "中國")
.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("中國[100000]", t4[0].path);
Assert.Equal("中國[100000] -> 北京[110000]", t4[1].path);
Assert.Equal("中國[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path);
Assert.Equal("中國[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" = '中國')
// 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