年关将至, 首页技术含量文章真是越来越少, 理解大家盼着放假过年, 哥们我何尝不是, 先给大家拜个早年.
兄弟我从 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/wiki
最后诚心邀请您的参与加入, 一起完成 FreeSql 的使命.
来源: https://www.cnblogs.com/kellynic/p/10310484.html