共计 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…