年关将至,首页技术含量文章真是越来越少,理解大家盼着放假过年,哥们我何尝不是,先给大家拜个早年。

兄弟我从11月底发了神经,开启了 ORM 功能库的开发之旅,历时两个月编码和文档整理,目前预览版本更新到 v0.0.9 仍是一个初级版本,怎奈今天把
wiki 文档更新到一半,突然想写一篇文章提前向大家介绍项目。

快过年还逼着您来了解新的技术,实在是抱歉了。介绍一下自己,有一些朋友可能知道我本人,我经常会在群里放开源项目,比如 dotnetGen、csredis
等,反感之余仍然希望可以真的帮助到有需要的人,CSRedisCore 正是本人维护的项目,本文介绍的不是它。

简单点介绍,FreeSql 是一个NETStandard ORM 功能库,采用 MIT 开源协议部署在 github。

它实现了的功能特性

* [x] CodeFirst 迁移。
* [x] DbFirst 从数据库导入实体类,支持三种模板生成器。
* [x] 采用 ExpressionTree 高性能读取数据。
* [x] 类型映射深入支持,比如pgsql的数组类型。
* [x] 支持丰富的表达式函数。
* [x] 支持导航属性查询,和延时加载。
* [x] 支持同步/异步数据库操作方法,丰富多彩的链式查询方法。
* [x] 支持事务。
* [x] 支持读写分离。
* [x] 支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite。
快速开始
var connstr = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;" +
"Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10"; IFreeSql
fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, connstr)
.UseSlave("connectionString1", "connectionString2") //使用从数据库,支持多个
.UseMonitorCommand( cmd => Console.WriteLine(cmd.CommandText), //监听SQL命令对象,在执行前
(cmd, traceLog) => Console.WriteLine(traceLog)) //监听SQL命令对象,在执行后
.UseLogger(null) //使用日志,不指定默认输出控制台 ILogger .UseCache(null) //使用缓存,不指定默认使用内存
IDistributedCache .UseAutoSyncStructure(true) //自动同步实体结构到数据库
.UseSyncStructureToLower(true) //转小写同步结构 .UseLazyLoading(true)
//延时加载导航属性对象,导航属性需要声明 virtual .Build();
定义实体

可从现有数据库生成实体模型,提供 IDbFirst 生成实体模型。或者手动创建模型,基于模型创建或修改数据库,提供 ICodeFirst 同步结构的
API(甚至可以做到开发阶段自动同步)。
[Table(Name = "tb_topic")] class Topic { [Column(IsIdentity = true, IsPrimary
= true)] public int Id { get; set; } public int Clicks { get; set; } public int
TestTypeInfoGuid { get; set; } public TestTypeInfo Type { get; set; } public
string Title { get; set; } public DateTime CreateTime { get; set; } } class
TestTypeInfo { public int Guid { get; set; } public int ParentId { get; set; }
public TestTypeParentInfo Parent { get; set; } public string Name { get; set; }
} class TestTypeParentInfo { public int Id { get; set; } public string Name {
get; set; } } ISelect<Topic> select => fsql.Select<Topic>();
查询

普通查询
var sql = select.Where(a => a.Id == 10).ToSql(); ///SELECT ... FROM `tb_topic`
a WHERE (a.`Id` = 10) sql = select.Where(a => a.Id == 10 && a.Id > 10 ||
a.Clicks > 100).ToSql(); ///SELECT ... FROM `tb_topic` a WHERE (a.`Id` = 10 AND
a.`Id` > 10 OR a.`Clicks` > 100) sql = select.Where(a => new
[]{1,2,3}.Contains(a.Id)).ToSql(); //SELECT ... FROM `tb_topic` a WHERE (a.`Id`
in (1,2,3))
每页20条数据,查询第1页
var sql = select.Page(1, 20).ToSql(); ///SELECT ... FROM `tb_topic` a LIMIT
0,20
利用导航属性联表
sql = select.LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid).ToSql();
//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a__Type.`Guid`,
a__Type.`ParentId`, a__Type.`Name`, a.`Title`, a.`CreateTime` //FROM `tb_topic`
a //LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid`
sql = select .LeftJoin(a => a.Type.Guid == a.TestTypeInfoGuid) .LeftJoin(a =>
a.Type.Parent.Id == a.Type.ParentId).ToSql(); //SELECT ... //FROM `tb_topic` a
//LEFT JOIN `TestTypeInfo` a__Type ON a__Type.`Guid` = a.`TestTypeInfoGuid`
//LEFT JOIN `TestTypeParentInfo` a__Type__Parent ON a__Type__Parent.`Id` =
a__Type.`ParentId`
子表 Exists 查询
var sql2222 = select.Where(a => select.Where(b => b.Id ==
a.Id).Any()).ToList(); // SELECT ... FROM `xxx` a WHERE (exists(SELECT 1 FROM
`xxx` b WHERE (b.`Id` = a.`Id`))) //两级相同的子表查询 sql2222 = select.Where(a =>
select.Where(b => b.Id == a.Id && select.Where(c => c.Id == b.Id).Where(d =>
d.Id == a.Id).Where(e => e.Id == b.Id) .Offset(a.Id) .Any() ).Any() ).ToList();
// SELECT ... FROM `xxx` a WHERE (exists(SELECT 1 FROM `xxx` b WHERE (b.`Id` =
a.`Id` AND exists(SELECT 1 FROM `xxx` c WHERE (c.`Id` = b.`Id`) AND (c.`Id` =
a.`Id`) AND (c.`Id` = b.`Id`) limit 0,1)) limit 0,1))
查找今天创建的数据
select.Where(a => a.CreateTime.Date == DateTime.Now.Date).ToSql();

支持功能丰富的表达式函数解析,包括(字符串、日期、时间、数学、类型转换)等函数,方便程序员在不了解数据库函数的情况下编写代码。这是非常特色的功能之一,深入细化函数解析尽量做到满意,所支持的类型基础都可以使用对应的表达式函数,例如
日期、字符串、IN查询、数组(PostgreSQL的数组)、字典(PostgreSQL HStore)等等。

获得查询返回的数据

采用 ExpressionTree 优化读取速读,如果懂技术的你一定知道 .NETCore 技术下除了原生代码,最快就是 Emit 和
ExpressionTree。项目在初期使用的反射+缓存,虽然 .NETCore
优化了反射性能,但经过与Dapper性能测试对比之后,发现仍然有一定差距,改成 ExpresstionTree 后才与 Dapper 性能相当。FreeSql
支持的类型较多,现实 ExpressionTree 的复杂度较大,有兴趣的朋友可以翻阅源代码。

返回单条记录
Topic t1 = select.Where(a => a.Id == 1).ToOne();
FreeSql有两个约定,ToOne 永远返回 null 或 有数据的实体对象,ToList 永远返回非 null 的 List<实体类型>

返回 List
List<Topic> t1 = select.Where(a => a.Id > 0).Skip(100).Limit(200).ToList();
返回 List + 导航属性的数据
List<Topic> t2 = select.LeftJoin(a => a.Type.Guid ==
a.TestTypeInfoGuid).ToList(); //此时会返回普通字段 + 导航对象 Type 的数据
指定字段返回
//返回一个字段 List<int> t3 = select.Where(a => a.Id >
0).Skip(100).Limit(200).ToList(a => a.Id); //返回匿名类 List<匿名类> t4 =
select.Where(a => a.Id > 0).Skip(100).Limit(200).ToList(a => new { a.Id,
a.Title }); //返回元组 List<(int, string)> t5 = select.Where(a => a.Id >
0).Skip(100).Limit(200).ToList<(int, string)>("id, title"); //返回SQL字段 List<匿名类>
t6 = select.Where(a => a.Id > 0).Skip(100).Limit(200) .ToList(a => new { a.Id,
a.Title, cstitle = "substr(a.title, 0, 2)", //将 substr(a.title, 0, 2) 作为查询字段
csnow = Convert.ToDateTime("now()"), //将 now() 作为查询字段 //奇思妙想:怎么查询开窗函数的结果 });
//返回更为复杂的结构 List<匿名类> t7 = select.From<TestTypeInfo, TestTypeParentInfo>((s, b,
c) => s .InnerJoin(a => a.TypeGuid == b.Guid) .LeftJoin(a => c.Id ==
b.ParentId) .Where(a => b.Name != "xxx")).ToList((a, b, c) => new { a.Id,
a.Title, a.Type, ccc = new { a.Id, a.Title }, tp = a.Type, tp2 = new { a.Id,
tp2 = a.Type.Name }, tp3 = new { a.Id, tp33 = new { a.Id } } });
执行SQL返回数据
class xxx { public int Id { get; set; } public string Path { get; set; }
public string Title2 { get; set; } } List<xxx> t8 = fsql.Ado.Query<xxx>("select
* from song"); List<(int, string ,string)> t7 = fsql.Ado.Query<(int, string,
string)>("select * from song"); List<dynamic> t9 =
fsql.Ado.Query<dynamic>("select * from song");
CodeFirst

支持 CodeFirst 迁移结构至数据库,这应该是(O/RM)必须标配的一个功能。


与其他(O/RM)不同FreeSql支持更多的数据库特性,而不只是支持基础的数据类型,这既是优点也是缺点,优点是充分利用数据库特性辅助开发,缺点是切换数据库变得困难。不同程序员的理念可能不太一致,作为功能库FreeSql支持到了极致,至于是否使用是项目组技术衡量的另一个问题。


尽管多种数据库适配逻辑非常复杂,FreeSql始终秉承优化程序开发习惯的原则尽量去现实,中间碰到了一些非技术无法攻克的难题,比如数据库的自定义类型,和实体类本身就是一种冲突,为了减少使用成本,诸如此类的数据库功能没有得到支持。

除了在实体上标注特性,也支持实体以外配置
fsql.CodeFirst .ConfigEntity<TestFluenttb1>(a => {
a.Name("xxdkdkdk1").SelectFilter("a.Id22 > 0"); a.Property(b =>
b.Id).Name("Id22").IsIdentity(true); a.Property(b =>
b.name).DbType("varchar(100)").IsNullable(true); })
.ConfigEntity<TestFluenttb2>(a => { a.Name("xxdkdkdk2").SelectFilter("a.Idx >
0"); a.Property(b => b.Id).Name("Id22").IsIdentity(true); a.Property(b =>
b.name).DbType("varchar(100)").IsNullable(true); });
类型映射

csharp MySql SqlServer PostgreSQL Oracle Sqlite
bool | bool? bit(1) bit bool number(1) boolean
sbyte | sbyte? tinyint(3) smallint int2 number(4) smallint
short | short? smallint(6) smallint int2 number(6) smallint
int | int? int(11) int int4 number(11) integer
long | long? bigint(20) bigint int8 number(21) integer
byte | byte? tinyint(3) unsigned tinyint int2 number(3) int2
ushort | ushort? smallint(5) unsigned int int4 number(5) unsigned
uint | uint? int(10) unsigned bigint int8 number(10) decimal(10,0)
ulong | ulong? bigint(20) unsigned decimal(20,0) numeric(20,0) number(20)
decimal(21,0)
double | double? double float float8 float(126) double
float | float? float real float4 float(63) float
decimal | decimal? decimal(10,2) decimal(10,2) numeric(10,2) number(10,2)
decimal(10,2)
Guid | Guid? char(36) uniqueidentifier uuid char(36 CHAR) character(36)
TimeSpan | TimeSpan? time time time interval day(2) to second(6) bigint
DateTime | DateTime? datetime datetime timestamp timestamp(6) datetime
DateTimeOffset | DateTimeOffset? - - datetimeoffset timestamp(6) with local
time zone -
Enum | Enum? enum int int4 number(16) mediumint
FlagsEnum | FlagsEnum? set bigint int8 number(32) bigint
byte[] varbinary(255) varbinary(255) bytea blob blob
string varchar(255) nvarchar(255) varchar(255) nvarchar2(255) nvarchar(255)
MygisPoint point - - - -
MygisLineString linestring - - - -
MygisPolygon polygon - - - -
MygisMultiPoint multipoint - - - -
MygisMultiLineString multilinestring - - - -
MygisMultiPolygon multipolygon - - - -
BitArray - - varbit(64) - -
NpgsqlPoint | NpgsqlPoint? - - point - -
NpgsqlLine | NpgsqlLine? - - line - -
NpgsqlLSeg | NpgsqlLSeg? - - lseg - -
NpgsqlBox | NpgsqlBox? - - box - -
NpgsqlPath | NpgsqlPath? - - path - -
NpgsqlPolygon | NpgsqlPolygon? - - polygon - -
NpgsqlCircle | NpgsqlCircle? - - circle - -
(IPAddress Address, int Subnet) | (IPAddress Address, int Subnet)? - - cidr - -
IPAddress - - inet - -
PhysicalAddress - - macaddr - -
NpgsqlRange<int> | NpgsqlRange<int>? - - int4range - -
NpgsqlRange<long> | NpgsqlRange<long>? - - int8range - -
NpgsqlRange<decimal> | NpgsqlRange<decimal>? - - numrange - -
NpgsqlRange<DateTime> | NpgsqlRange<DateTime>? - - tsrange - -
PostgisPoint - - geometry - -
PostgisLineString - - geometry - -
PostgisPolygon - - geometry - -
PostgisMultiPoint - - geometry - -
PostgisMultiLineString - - geometry - -
PostgisMultiPolygon - - geometry - -
PostgisGeometry - - geometry - -
PostgisGeometryCollection - - geometry - -
Dictionary<string, string> - - hstore - -
JToken - - jsonb - -
JObject - - jsonb - -
JArray - - jsonb - -
数组 - - 以上所有类型都支持 - -
以上类型和长度是默认值,可手工设置,如 string 属性可指定 [Column(DbType = "varchar(max)")]

DbFirst

FreeSql 同样支持 DbFirst
的开发模式,即先有数据库再有项目,这种传统的开发模式永远不会过时,特别适合老项目仍然在维护,或者数据库重构成本过高的项目。关系型数据库有许多成熟的运维和开发类配套工具,例如我们所关注的ER图工具
PowerdeSigner,使用它可以更直观的查看表之间的关系。除了FreeSql作者同时在维护超过十年的代码生成器dotnetGen
<https://github.com/2881099/dotnetGen_postgresql>项目,它是一款支持超快速开发且高度可控的解决方案,非常喜欢
DbFirst 的开发模式,因此在 FreeSql for DbFirst 功能上会有不错的支持。

DbFirst 模式开发主要提供了不同数据库的表结构查询适配,配合模板生成器现实从数据库导入模型到c#代码中。

生成器是基于 DbFirst 开发的辅助工具,适用老项目一键生成实体。生成器采用模板的方式,实现了三种生成模板:

模板名称 路径 类型映射 外键导航属性 缓存管理 失血 贫血 充血
simple-entity ../Templates/MySql/simple-entity √ X X √ X X
simple-entity-navigation-object
../Templates/MySql/simple-entity-navigation-object √ √ X √ X X
rich-entity-navigation-object ../Templates/MySql/rich-entity-navigation-object
√ √ √ X √ X
结语

项目功能与文档较多,许多细节不适合在一篇文章中展示详尽,更多的功能介绍请移步 github wiki
中心。借此文章介绍FreeSql这个国产ORM,希望能得到大家的支持。

FreeSql 口号:打造 .NETCore 最方便的 ORM!

项目地址:https://github.com/2881099/FreeSql <https://github.com/2881099/FreeSql>

文档中心:https://github.com/2881099/FreeSql/wiki
<https://github.com/2881099/FreeSql/wiki>

最后诚心邀请您的参与加入,一起完成 FreeSql 的使命。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信