【纯技术贴】.NETStandardFreeSqlv0.0.9功能预览
年关将至,首页技术含量文章真是越来越少,理解大家盼着放假过年,哥们我何尝不是,先给大家拜个早年。
兄弟我从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
-
-
point
-
-
NpgsqlLine
-
-
line
-
-
NpgsqlLSeg
-
-
lseg
-
-
NpgsqlBox
-
-
box
-
-
NpgsqlPath
-
-
path
-
-
NpgsqlPolygon
-
-
polygon
-
-
NpgsqlCircle
-
-
circle
-
-
(IPAddress Address, int Subnet)
-
-
cidr
-
-
IPAddress
-
-
inet
-
-
PhysicalAddress
-
-
macaddr
-
-
NpgsqlRange<int>
-
-
int4range
-
-
NpgsqlRange<long>
-
-
int8range
-
-
NpgsqlRange<decimal>
-
-
numrange
-
-
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项目,它是一款支持超快速开发且高度可控的解决方案,非常喜欢 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/wiki
最后诚心邀请您的参与加入,一起完成 FreeSql 的使命。
以上是 【纯技术贴】.NETStandardFreeSqlv0.0.9功能预览 的全部内容, 来源链接: utcz.com/z/510911.html