[開源] .Net ORM FreeSql 1.10.1 穩(wěn)步向前

寫在開頭

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)見下圖:

image

年底發(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ù)造輪子娜膘?

image

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ù)的功能;

十二夹厌、增加 父子表(樹表)遞歸查詢豹爹、刪除功能;

image

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 其他全部是親兒子,在功能提供方面一碗水端平秋泳。

image

眾所周知 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)過濾查詢

image

是否見過這樣的高級(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

原文地址:https://www.cnblogs.com/kellynic/p/13855784.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宪肖,一起剝皮案震驚了整個(gè)濱河市表制,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌控乾,老刑警劉巖么介,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阱持,居然都是意外死亡夭拌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門衷咽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸽扁,“玉大人,你說我怎么就攤上這事镶骗⊥跋郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鼎姊,是天一觀的道長(zhǎng)骡和。 經(jīng)常有香客問我相赁,道長(zhǎng),這世上最難降的妖魔是什么慰于? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任钮科,我火速辦了婚禮,結(jié)果婚禮上婆赠,老公的妹妹穿的比我還像新娘绵脯。我一直安慰自己,他們只是感情好休里,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布蛆挫。 她就那樣靜靜地躺著,像睡著了一般妙黍。 火紅的嫁衣襯著肌膚如雪悴侵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天拭嫁,我揣著相機(jī)與錄音可免,去河邊找鬼。 笑死噩凹,一個(gè)胖子當(dāng)著我的面吹牛巴元,可吹牛的內(nèi)容都是我干的毡咏。 我是一名探鬼主播驮宴,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼呕缭!你這毒婦竟也來了堵泽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤恢总,失蹤者是張志新(化名)和其女友劉穎迎罗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體片仿,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纹安,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砂豌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厢岂。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖阳距,靈堂內(nèi)的尸體忽然破棺而出塔粒,到底是詐尸還是另有隱情,我是刑警寧澤筐摘,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布卒茬,位于F島的核電站船老,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圃酵。R本人自食惡果不足惜柳畔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郭赐。 院中可真熱鬧荸镊,春花似錦、人聲如沸堪置。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舀锨。三九已至岭洲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坎匿,已是汗流浹背盾剩。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留替蔬,地道東北人告私。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像承桥,于是被迫代替她去往敵國和親驻粟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355