关于.net:开源-Net-ORM-FreeSql-1101-稳步向前

51次阅读

共计 14445 个字符,预计需要花费 37 分钟才能阅读完成。

写在结尾

FreeSql 是 .NET 开源生态下的 ORM 轮子,转瞬快两年了,说真的开源不容易(只有经验过才明确)。明天带点干货和湿货给大家,先说下湿货。

意识我的人,晓得 CSRedisCore 是我写的另外一个开源组件,这个我的项目是 2016 年从 ctstone/csredis 我的项目 clone 到本人工作的我的项目中,批改源码通过一年多生产考验,于 2017 年公布开源 https://github.com/2881099/cs…

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…

正文完
 0