[開源] .Net ORM FreeSql 1.10.0 穩步向前

nicye發表於2020-10-22

寫在開頭

FreeSql 是 .NET 開源生態下的 ORM 輪子,轉眼快兩年了,說真的開源不容易(只有經歷過才明白)。今天帶點乾貨和溼貨給大家,先說下溼貨。

認識我的人,知道 CSRedisCore 是我寫的另外一個開源元件,這個專案是 2016 年從 ctstone/csredis 專案 clone 到自己工作的專案中,修改原始碼經過一年多生產考驗,於 2017 年釋出開源 https://github.com/2881099/csredis

ctstone/csredis 專案於 2014 年停止了更新,到我手裡完善的功能如下:

  • 連線池
  • 哨兵高可用
  • 叢集
  • redis 2.8 以上的版本命令補充,包括 Geo、Stream
  • 通訊協議 bug 修復

暫時想到的只有這些,之後可能再補充。FreeSql 文章標題為什麼要來說 csredis?

這兩年的時間裡 95% 精力都用在了 FreeSql 上面, 5400+ 單元測試、支援十幾種資料庫適配,渣男辜負了 csredis 這個專案。最近一個多月開源圈子的奇葩事接二連三,居然有人跑去 ctstone/csredis 原作者的 issues 告我的狀,這個告狀的人還是 NOPI 原作者,因為當初他自己不維護 NPOI .NET Core 版本了,社群有好人把 .NET Core 版本測試做好了開源(dotnetcore/NPOI),告狀的人很真心厲害,已經成功把 nuget.org/dotnetcore.npoi 整下架了。

他並沒有得到滿足,之後開始針對整個 NCC 社群成員,包括我。

  • 他去了 sqlsugar issues 發表,說要找出 FreeSql 抄襲 sqlsugar 的證據
  • 他又去 fur issues 發表聲援,說我黑他
  • 他還去 csredis 原作者 issues 釋出內容,企圖告我的狀

並不是人人都像你一樣,強迫要求下游專案“歸檔”、“制裁”,試問 mysql 可以要求 mariadb 歸檔?針對 NCC 組織還是針對我本人?CSRedisCore 並不在 NCC 開源組織下!!!

幾天月前我已經開始了新的 redis .NET 開源元件庫的編寫,完全自主的看你能上哪裡告狀。有了這麼長時間的 csredis 經驗,重新寫一個能避免很多問題,設計也會更好,後面我會花大部分時間做新專案,這便是今天帶來的溼貨,敬請期待發布!~!

入戲準備

2018 年 12 月份開發 FreeSql 到現在,2200 顆星,500 Issues,200K 包下載量。說明還是有開發者關注和喜愛,只要有人關注,就不會停更不修 BUG 一說。大家有興趣可以看看更新記錄,看看我們的程式碼提交量,5400+ 單元測試不說非常多,個人覺得已經超過很多國產專案。

23個月了,FreeSql 還活著,而且生命力頑強見下圖:

年底釋出 2.0 版本正在收集需求中(歡迎前去 issues 誠意登記),本文將介紹在過去的幾個月完成的一些有意義的功能介紹。

FreeSql 是 .Net ORM,能支援 .NetFramework4.0+、.NetCore、Xamarin、XAUI、Blazor、以及還有說不出來的執行平臺,因為程式碼綠色無依賴,支援新平臺非常簡單。目前單元測試數量:5400+,Nuget下載數量:200K+,原始碼幾乎每天都有提交。值得高興的是 FreeSql 加入了 ncc 開源社群:https://github.com/dotnetcore/FreeSql,加入組織之後社群責任感更大,需要更努力做好品質,為開源社群出一份力。

QQ群:4336577(已滿)、8578575(線上)、52508226(線上)

為什麼要重複造輪子?

FreeSql 主要優勢在於易用性上,基本是開箱即用,在不同資料庫之間切換相容性比較好。作者花了大量的時間精力在這個專案,肯請您花半小時瞭解下專案,謝謝。

FreeSql 整體的功能特性如下:

  • 支援 CodeFirst 對比結構變化遷移;
  • 支援 DbFirst 從資料庫匯入實體類;
  • 支援 豐富的表示式函式,自定義解析;
  • 支援 批量新增、批量更新、BulkCopy;
  • 支援 導航屬性,貪婪載入、延時載入、級聯儲存;
  • 支援 讀寫分離、分表分庫,租戶設計;
  • 支援 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/達夢/神通/人大金倉/MsAccess Ado.net 實現包,以及 Odbc 的專門實現包;

乾貨來了

1.5.0 -> 1.10.0 更新的重要功能如下:

一、增加 Firebird 資料庫實現;

二、增加 人大金倉/神通 資料庫的訪問支援;

三、增加 GlobalFilter.ApplyIf 建立動態過濾器;

四、增加 ISelect.InsertInto 將查詢轉換為 INSERT INTO t1 SELECT ... FROM t2 執行插入;

五、增加 IncludeMany(a => a.Childs).ToList(a => new { a.Childs }) 指定集合屬性返回;

六、增加 $"{a.Code}_{a.Id}" lambda 解析;

七、增加 lambda 表示式樹解析子查詢 ToList + string.Join() 產生 類似 group_concat 的效果;

八、增加 SqlExt 常用開窗函式的自定義表示式解析;

九、增加 ISelect/IInsert/IUpdate/IDelete CommandTimeout 方法設定命令超時;

十、完善 WhereDynamicFilter 動態過濾查詢;

十一、增加 BeginEdit/EndEdit 批量編輯資料的功能;

十二、增加 父子表(樹表)遞迴查詢、刪除功能;

FreeSql 使用非常簡單,只需要定義一個 IFreeSql 物件即可:

static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.MySql, connectionString)
    .UseAutoSyncStructure(true) //自動同步實體結構到資料庫
    .Build(); //請務必定義成 Singleton 單例模式

增加 Firebird 資料庫實現;

它的體積比前輩Interbase縮小了幾十倍,但功能並無閹割。為了體現Firebird短小精悍的特色,開發小組在增加了超級伺服器版本之後,又增加了嵌入版本,最新版本為2.0。Firebird的嵌入版有如下特色:

1、資料庫檔案與Firebird網路版本完全相容,差別僅在於連線方式不同,可以實現零成本遷移。
2、資料庫檔案僅受作業系統的限制,且支援將一個資料庫分割成不同檔案,突破了作業系統最大檔案的限制,提高了IO吞吐量。
3、完全支援SQL92標準,支援大部分SQL-99標準功能。
4、豐富的開發工具支援,絕大部分基於Interbase的元件,可以直接使用於Firebird。
5、支援事務、儲存過程、觸發器等關聯式資料庫的所有特性。
6、可自己編寫擴充套件函式(UDF)。
7、firebird其實並不是純粹的嵌入式資料庫,embed版只是其眾多版本中的一個。不過做的也很小,把幾個dll加起來才不到5M,但是它支援絕大部份SQL92與SQL99標準

嵌入式,等於無需安裝的本地資料庫,歡迎體驗!~~


增加 人大金倉/神通 資料庫的訪問支援

天津神舟通用資料技術有限公司(簡稱“神舟通用公司”),隸屬於中國航天科技集團(CASC)。是國內從事資料庫、大資料解決方案和資料探勘分析產品研發的專業公司。公司獲得了國家核高基科技重大專項重點支援,是核高基專項的牽頭承擔單位。自1993年在航天科技集團開展資料庫研發以來,神通資料庫已歷經27年的發展歷程。公司核心產品主要包括神通關係型資料庫、神通KStore海量資料管理系統、神通商業智慧套件等系列產品研發和市場銷售。基於產品組合,可形成支援交易處理、MPP資料庫叢集、資料分析與處理等解決方案,可滿足多種應用場景需求。產品通過了國家保密局涉密資訊系統、公安部等保四級、軍B +級等安全評測和認證。

北京人大金倉資訊科技股份有限公司(以下簡稱“人大金倉”)是具有自主智慧財產權的國產資料管理軟體與服務提供商。人大金倉由中國人民大學一批最早在國內開展資料庫教學、科研、開發的專家於1999年發起創立,先後承擔了國家“863”、“核高基”等重大專項,研發出了具有國際先進水平的大型通用資料庫產品。2018年,人大金倉申報的“資料庫管理系統核心技術的創新與金倉資料庫產業化”專案榮獲2018年度國家科學技術進步二等獎,產學研的融合進一步助力國家資訊化建設。

隨著華為、中興事務,國產資料庫市場相信是未來是趨勢走向,縱觀 .net core 整個圈子對國產神舟通用、人大金倉資料庫的支援幾乎為 0,今天 FreeSql ORM 可以使用 CodeFirst/DbFirst 兩種模式進行開發。

並且聲稱:FreeSql 對各資料庫沒有親兒子一說,除了 MsAcces 其他全部是親兒子,在功能提供方面一碗水端平。

眾所周知 EFCore for oracle 問題多,並且現在才剛剛更新到 3.x,在這樣的背景下,一個國產資料庫更不能指望誰實現好用的 EFCore。目前看來除了 EFCore for sqlserver 我們沒把握完全佔優勢,起碼在其他資料庫肯定是我們更接地氣。

使用 FreeSql 訪問人大金倉/神通 資料庫,只需要修改程式碼如下即可:

static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
    .UseConnectionString(FreeSql.DataType.ShenTong, connectionString) //修改 DataType 設定切換資料庫
    .UseAutoSyncStructure(true) //自動同步實體結構到資料庫
    .Build(); //請務必定義成 Singleton 單例模式

增加 GlobalFilter.ApplyIf 建立動態過濾器;

FreeSql 使用全域性過濾器非常簡單,我們的過濾器支援多表查詢、子查詢,只需要設定一次:

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);

上面增加了兩個過濾器,tenant 第二個引數正是增加的功能,當委託條件成立時才會附加過濾器。


增加 ISelect.InsertInto 將查詢轉換為 INSERT INTO t1 SELECT ... FROM t2 執行插入;

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

注意:因為 Clicks、CreateTime 沒有被選擇,所以使用目標實體屬性 [Column(InsertValueSql = xx)] 設定的值,或者使用目標實體屬性的 c# 預設值。

又一次完善了批量運算元據的功能,之前已經有的功能如下:

  • fsql.InsertOrUpdate 相當於 Merge Into/on duplicate key update
Database Features Database Features
MySql on duplicate key update 達夢 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(陣列).ExecuteAffrows() 相當於批量插入
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)

當插入大批量資料時,內部採用分割分批執行的邏輯進行。分割規則:

數量 引數量
MySql 5000 3000
PostgreSQL 5000 3000
SqlServer 1000 2100
Oracle 500 999
Sqlite 5000 999
  • fsql.Insert(陣列).ExecuteSqlBulkCopy、ExecutePgCopy、ExecuteMySqlBulkCopy

  • fsql.Update<T>().SetSource(陣列).ExecuteAffrows() 相當於批量更新


增加 IncludeMany(a => a.Childs).ToList(a => new { a.Childs }) 指定集合屬性返回;

這個功能實在太重要了,在此之前 IncludeMany 和 ToList(指定欄位) 八字不合,用起來有些麻煩。現在終於解決了!!~~

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 解析;

在之前查詢資料的時候,$"" 這種語法糖神器居然不能使用在 lambda 表示式中,實屬遺憾。現在終於可以了,如下:

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);

再次說明:都是親兒子,並且都有對應的單元測試,兄臺大可放心用在不同的資料庫中


增加 lambda 表示式樹解析子查詢 ToList + string.Join() 產生 類似 group_concat 的效果;

v1.8.0+ string.Join + ToList 實現將子查詢的多行結果,拼接為一個字串,如:"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

該語法,在不同資料庫都作了相應的 SQL 翻譯。


增加 SqlExt 常用的自定義表示式樹解析;

SqlExt.cs 定義了一些常用的表示式樹解析,如下:

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(), //這裡因為複雜才這樣,一般使用三元表示式即可:a.Id == 1 ? 10 : 11
  groupct1 = SqlExt.GroupConcat(a.Id).Distinct().OrderBy(b.EdiId).Separator("_").ToValue()
  });

本功能利用 FreeSql 自定義解析實現常用表示式樹解析,歡迎 PR 補充


增加 ISelect/IInsert/IUpdate/IDelete CommandTimeout 方法設定命令超時;

現在每條 crud 都可以設定命令執行的超時值,如下:

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 動態過濾查詢

是否見過這樣的高階查詢功能,WhereDynamicFilter 在後端可以輕鬆完成這件事情,前端根據 UI 組裝好對應的 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 方法實現動態過濾條件(與前端互動),支援的操作符:

  • 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 中任何一項(直白的說是 SQL IN)

增加 BeginEdit/EndEdit 批量編輯資料的功能;

場景:winform 載入表資料後,一頓新增、修改、刪除操作之後,點選【儲存】

[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); //開始對 cts 進行編輯

    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 方法執行的時候產生 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')

提醒:該操作只對變數 cts 有效,不是針對全表對比更新。


增加 父子表(樹表)遞迴查詢、刪除功能;

無限級分類(父子)是一種比較常用的表設計,每種設計方式突出優勢的同時也帶來缺陷,如:

  • 方法1:表設計中只有 parent_id 欄位,困擾:查詢麻煩(本文可解決);
  • 方法2:表設計中冗餘子級id便於查詢,困擾:新增/更新/刪除的時候需要重新計算;
  • 方法3:表設計中儲存左右值編碼,困擾:同上;

方法1設計最簡單,我們正是解決它設計簡單,使用複雜的問題。

首先,按照導航屬性的定義,定義好父子屬性:

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 屬性,在表示式中可以這樣:

fsql.Select<Area>().Where(a => a.Parent.Parent.Parent.Name == "中國").First();

定義 Childs 屬性,在表示式中可以這樣(子查詢):

fsql.Select<Area>().Where(a => a.Childs.AsSelect().Any(c => c.Name == "北京")).First();

定義 Childs 屬性,還可以使用【級聯儲存】【貪婪載入】 等等操作。

利用級聯儲存,新增測試資料如下:

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 = "東城區" },
      })
    }
  })
});

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

查詢資料本來是平面的,ToTreeList 方法將返回的平面資料在記憶體中加工為樹型 List 返回。

功能2:AsTreeCte 遞迴刪除

很常見的無限級分類表功能,刪除樹節點時,把子節點也處理一下。

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 遞迴查詢

若不做資料冗餘的無限級分類表設計,遞迴查詢少不了,AsTreeCte 正是解決遞迴查詢的封裝,方法引數說明:

引數 描述
(可選) pathSelector 路徑內容選擇,可以設定查詢返回:中國 -> 北京 -> 東城區
(可選) up false(預設):由父級向子級的遞迴查詢,true:由子級向父級的遞迴查詢
(可選) pathSeparator 設定 pathSelector 的連線符,預設:->
(可選) level 設定遞迴層級

通過測試的資料庫:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、達夢、人大金倉

姿勢一:AsTreeCte() + ToTreeList

var t2 = fsql.Select<Area>()
  .Where(a => a.Name == "中國")
  .AsTreeCte() //查詢 中國 下的所有記錄
  .OrderBy(a => a.Code)
  .ToTreeList(); //非必須,也可以使用 ToList(見姿勢二)
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"

姿勢二: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);
//執行的 SQL 與姿勢一相同

姿勢三:AsTreeCte(pathSelector) + ToList

設定 pathSelector 引數後,如何返回隱藏欄位?

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] -> 東城區[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"

更多姿勢...請根據程式碼註釋進行嘗試

寫在最後

給 .NET 開源社群貢獻一點力時,希望作者的努力能打動到你,請求正在使用的、善良的您能動一動小手指,把文章轉發一下,讓更多人知道 .NET 有這樣一個好用的 ORM 存在。謝謝了!!

FreeSql 使用最寬鬆的開源協議 MIT https://github.com/dotnetcore/FreeSql,完全可以商用,文件齊全。QQ群:4336577(已滿)、8578575(線上)、52508226(線上)

如果你有好的 ORM 實現想法,歡迎給作者留言討論,謝謝觀看!

2.0 版本意見正在登記中:https://github.com/dotnetcore/FreeSql/issues/469

相關文章