一、簡介
FreeSql 是 .NET 平台下的對象關系映射技術(O/RM),支援 .NetCore 2.1+ 或 .NetFramework 4.0+ 或 Xamarin。
從 0.0.1 釋出,曆時整整一年的疊代更新,原計劃元旦釋出1.0,可能作者比較急提前了幾天釋出。其實是元旦有其他事……
本文内容從簡,介紹項目的主要功能架構,以及暫時能想到的可能比較有說服力的特性。
二、項目統計
主倉庫解決方案共計項目:29個
單元測試:3510個
Code Issues:168個
文檔Wiki:43個
Stars:1140
Forks:236
Commits:690次
Nuget主包下載下傳量:86,568次
開源位址:https://github.com/2881099/FreeSql
三、功能結構

- 支援 CodeFirst 遷移,哪怕使用 Access 資料庫也支援;
- 支援 DbFirst 從資料庫導入實體類;
- 支援 深入的類型映射,比如pgsql的數組類型;
- 支援 豐富的表達式函數,以及靈活的自定義解析;
- 支援 導航屬性一對多、多對多貪婪加載,以及延時加載;
- 支援 讀寫分離、分表分庫,租戶設計,過濾器,樂觀鎖,悲觀鎖;
- 支援 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/達夢資料庫/Access;
四、CodeFirst/DbFirst
一切皆 CodeFirst,所有功能都是由實體類型,到表操作的過程。CodeFirst 【自動遷移】隻需要一行代碼:
using FreeSql;
static IFreeSql fsql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite,
@"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10")
.UseAutoSyncStructure(true) //自動同步實體結構到資料庫
.Build();
在開發過程中,表結構會自動建立、或改變(不丢資料),取決于實體類的變化。
CodeFirst 提供功能豐富的特性ColumnAttribute,定義實體與表間的映射,并且支援 FluentApi 方式。如果不喜歡 ColumnAttribute 這個名字,還可以通過 AOP 設定換為 MyColumnAttribute。
using FreeSql.DataAnnotations;
class Song {
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public DateTime CreateTime { get; set; }
}
DbFirst 資料表先行,許多哥們使用動軟、T4模闆生成實體類代碼。自已處理每種資料庫的字段類型,和 csharp 類型對應,比較麻煩,各大 ORM 可能還不通用。
我們提供指令行工具生成實體類,dotnet-tools,對就是它。。非常好用的工具,沒有之一。
C:\Users\28810>dotnet tool install -g freesql.generator
可使用以下指令調用工具: FreeSql.Generator
已成功安裝工具“freesql.generator”(版本“1.0.0”)。
C:\Users\28810>FreeSql.Generator —help
它基于 Razor 模闆生成,支援自定義模闆生成,意味着它遠不止可以生成實體類,甚至是 IRepository 或者。。。
五、導航屬性
從一開始就着重導航對象的設計,支援一對多、多對多、父子關系、一對一、多對一,不誇張的說目前對導航屬性處理最流弊,最容易上手的 ORM。多表查詢的表達式使用非常便利,如下:
fsql.Select<Catetory>()
.Where(a => a.Parent.Parent.Name == "粵語")
可以使用導航屬性一直這樣點下去。。。
級聯儲存,級聯查詢功能也必不可少,如下查詢多對多:
fsql.Select<Song>()
.IncludeMany(a => a.Tags)
.ToList();
上面的代碼,如果隻傳回 Tags 前 5條記錄,也是支援的 .IncludeMany(a => a.Tags.Take(5))
對性能有追求,還可以指定 Tags 隻查詢部分字段
關于 IncludeMany 不便再這過多展開介紹。。。(其實還有黑科技!)
哦,還有 FreeSql.AdminLTE 擴充包,它不屬于主倉庫項目,最大化利用導航屬性完成通用的 CURD 背景管理功能。
流弊哒哒~~~~
六、倉儲模式
倉儲工作單元目前是當下的流行風,在比較早的時候大約0.2版本釋出了第一個倉儲版本,當時參考了大量的項目設計,最終選用 abp vnext 的 IRepository 設計接口,實作通用倉儲類功能。
也就是說,使用 FreeSql.Repository 你不必再自己寫那些繁瑣的 CURD 重複的倉儲功能,不用再頭疼倉儲類的接口方法定義。定義标準比寫代碼難多了,abp vnext 的 IRepository 目前是見過最好的,木有之一!!
倉儲模式都在操作實體對象,無論是更新還是删除,都是傳對象。。。傳傳傳。。。
問題1、傳對象更新,意味着更新所有字段?
不會的,我們的倉儲實作擁有狀态管理機制,從對象查詢出來的時候已經記錄了拍照,當調用更新方法的時候會與之對比,計算出變化的字段,隻更新變化的字段!
var repo = fsql.GetRepository<Song>();
var item = repo.Where(a => a.Id == 1).First();
item.Title = "原諒我今天";
repo.Update(item);
提示:支援樂觀鎖、悲觀鎖
問題2、狀态管理是否影響性能?
不完全,因為狀态管理設計在倉儲實作之上,我們最原始的 IFreeSql 沒有這個功能(倉儲算是一種擴充包吧,但是倉儲又非常有效)。倉儲即用即銷毀,擅用它的對比功能更新對象,不濫用沒有性能問題。
有了倉儲怎麼會沒有 UnitOfWork 呢,UnitOfWork 目前以事務的方式做了預設實作,并且它擁有實體變化跟蹤記錄。
七、性能
1、插入測試(52個字段)
18W | 1W | 5K | 2K | 1K | 500 | 100 | 50 | |
---|---|---|---|---|---|---|---|---|
MySql 5.5 ExecuteAffrows | 55,497 | 4,953 | 2,304 | 2,554 | 1,516 | 1,572 | 265 | 184 |
SqlServer Express ExecuteAffrows | 402,355 | 24,847 | 11,465 | 4,971 | 2,437 | 915 | 138 | 88 |
SqlServer Express ExecuteSqlBulkCopy | 21,065 | 578 | 326 | 139 | 105 | 79 | 60 | 48 |
PostgreSQL 10 ExecuteAffrows | 46,756 | 3,294 | 2,269 | 1,019 | 374 | 209 | 51 | 37 |
PostgreSQL 10 ExecutePgCopy | 10,090 | 583 | 337 | 136 | 61 | 30 | 25 | |
Oracle XE ExecuteAffrows | - | 24,528 | 10,648 | 571 | 200 | |||
Sqlite ExecuteAffrows | 28,554 | 1,149 | 701 | 327 | 155 | 91 | 44 | 35 |
測試結果,是在相同作業系統下進行的,并且都有預熱
18W 解釋:插入18萬行記錄,表格中的數字是執行時間(機關ms)
Oracle 插入性能不用懷疑,可能安裝學生版限制較大
提醒:開源資料庫測試結果比較有意義,商業資料庫版本之間性能可能有較大差距
2、插入測試(10個字段)
15,380 | 1,813 | 1,457 | 1,254 | 563 | 246 | 55 | 21 | |
47,204 | 2,275 | 1,108 | 488 | 279 | 123 | 16 | ||
4,248 | 127 | 71 | 14 | 11 | 10 | |||
9,786 | 568 | 336 | 157 | 102 | 34 | 9 | 6 | |
4,081 | 167 | 93 | 39 | 12 | 4 | 2 | ||
2,394 | 731 | 67 | 33 | |||||
4,524 | 137 | 94 | 19 |
提示:已經支援了 SqlServer 資料庫的 SqlBulkCopy 功能、以及 PostgreSQL 資料庫的 Copy 功能
八、拉姆達
非常特色的功能之一,深入細化函數解析,所支援的類型基本都可以使用對應的表達式函數,例如 日期、字元串、IN查詢、數組(PostgreSQL的數組)、字典(PostgreSQL HStore)等等。
1、In查詢
var t1 = fsql.Select<T>()
.Where(a => new[] { 1, 2, 3 }.Contains(a.Id))
.ToSql();
//SELECT .. FROM ..
//WHERE (a.`Id` in (1,2,3))
已優化,防止 where in 元素多過的 SQL 錯誤,如:
[Err] ORA-01795: maximum number of expressions in a list a 1000
原來:where id in (1..1333)
現在:where id in (1..500) or id in (501..1000) or id in (1001..1333)
2、In查詢(多列)
//元組集合
vae lst = new List<(Guid, DateTime)>();
lst.Add((Guid.NewGuid(), DateTime.Now));
lst.Add((Guid.NewGuid(), DateTime.Now));
lst.Add((Guid.NewGuid(), DateTime.Now));
fsql.Select<T>()
.Where(a => lst.Contains(a.Id, a.ct1))
.ToSql();
//SELECT .. FROM ..
//WHERE (a."Id" = '685ee1f6-bdf6-4719-a291-c709b8a1378f' AND a."ct1" = '2019-12-07 23:55:27' OR
//a."Id" = '5ecd838a-06a0-4c81-be43-1e77633b7404' AND a."ct1" = '2019-12-07 23:55:27' OR
//a."Id" = 'b8b366f3-1c03-4547-9c96-d362dd5cae6a' AND a."ct1" = '2019-12-07 23:55:27')
3、自定義函數
預設已經支援了很豐富的函數解析,如果不夠再自己定義:
[ExpressionCall]
public static class DbFunc
{
//必要定義 static + ThreadLocal
static ThreadLocal<ExpressionCallContext> context = new ThreadLocal<ExpressionCallContext>();
public static DateTime FormatDateTime(this DateTime that, string arg1)
{
var up = context.Value;
if (up.DataType == FreeSql.DataType.Sqlite) //重寫内容
context.Value.Result = $"date_format({up.ParsedContent["that"]}, {up.ParsedContent["arg1"]})";
return that;
}
}
fsql.Select<T>().ToSql(a => a.CreateTime.FormatDateTime("yyyy-MM-dd"));
//SELECT date_format(a."CreateTime", 'yyyy-MM-dd') as1
//FROM "T" a
提示:SqlServer nvarchar/varchar 已相容表達式解析,分别解析為:N'' 和 '',優化索引執行計劃
九、騷操作
1、代碼注釋 -> 遷移到資料庫
CodeFirst 支援将 c# 代碼内的注釋,遷移至資料庫的備注。先決條件:
- 實體類所在程式集,需要開啟 xml 文檔功能;
- xml 檔案必須與程式集同目錄,且檔案名:xxx.dll -> xxx.xml;
2、NoneParameter
可以設定不使用 參數化 執行 SQL 指令,友善開發調試,差別如下:
INSERT INTO `tb_topic`(`Title`) VALUES(?Title0)
INSERT INTO `tb_topic`(`Title`) VALUES('Title_1')
在 new FreeSqlBuilder().UseNoneParameter(true) 全局設定
在 單次 ISelect、IInsert、IDelete、IUpdate 上使用 NoneParameter() 設定單次生效
3、Dto 映射查詢
用過 ProjectTo 功能嗎?沒用過當忽略此行。。。
有些朋友可能是先 ToList().Mapper<T>(),這樣會先查詢了所有字段。
Dto 映射查詢支援單表/多表,這個功能可以決定隻查詢部分字段(不是、不是、不是先查詢所有字段再到記憶體映射)。
規則:查找屬性名,會循環内部對象 _tables(多表會增長),以 主表優先查,直到查到相同的字段。
如:A, B, C 都有 id,Dto { id, a1, a2, b1, b2 },A.id 被映射。也可以指定 id = C.id 映射。
fsql.Select<Song>().ToList(a => new DTO { xxx = a.ext })
//情況1:附加所有映射,再額外映射 ext,傳回 List<DTO>
fsql.Select<Song>().ToList(a => new Song { id = a.id })
//情況2:隻查詢 id,傳回 List<Song>
fsql.Select<Song>().ToList(a => new { id = a.id })
//情況3:隻查詢 id,傳回 List<匿名對象>
fsql.Select<Song>().ToList(a => new DTO(a.id))
//情況4:隻查詢 id,傳回 List<DTO>
fsql.Select<Song>().ToList(a => new DTO(a.id) { xxx = a.ext })
//情況5:查詢 id, ext,傳回 List<DTO>
fsql.Select<Song>().ToList(a => new Song(a.id))
//情況6:查詢 id,傳回 List<Song>
fsql.Select<Song>().ToList(a => new Song(a.id) { xxx = a.ext })
//情況7:查詢 id, ext,傳回 List<Song>
4、WhereCascade
FreeSql 擅長多表查詢,遇到像isdeleted每個表都給條件的時候,挺麻煩。WhereCascade使用後生成sql時,所有表都附上這個條件。
如:
fsql.Select<t1>()
.LeftJoin<t2>(...)
.WhereCascade(x => x.IsDeleted == false)
.ToList();
得到的 SQL:
SELECT ...
FROM t1
LEFT JOIN t2 on ... AND (t2.IsDeleted = 0)
WHERE t1.IsDeleted = 0
其中的實體可附加表達式時才生效,支援子表查詢。單次查詢使用的表數目越多收益越大。
5、審計 CURD
如果因為某個 sql 騷操作耗時很高,沒有一個相關的審計功能,排查起來可以說無從下手。
FreeSql 支援簡單的類似功能:
fsql.Aop.CurdAfter = (s, e) => {
if (e.ElapsedMilliseconds > 200) {
//記錄日志
//發送短信給負責人
}
};
隻需要一個事件,就可以對全局起到作用。
還有一個 CurdBefore 在執行 sql 之前觸發,常用于記錄日志或開發調試。
6、審計屬性值
實作插入/更新時統一處理某些值,比如某屬性的雪花算法值、建立時間值、甚至是業務值。
fsql.Aop.AuditValue += (s, e) => {
if (e.Column.CsType == typeof(long)
&& e.Property.GetCustomAttribute<SnowflakeAttribute>(false) != null
&& e.Value?.ToString() == 0)
e.Value = new Snowflake().GetId();
};
class Order {
[Snowflake]
public long Id { get; set; }
//...
}
當屬性的類型是 long,并且标記了 [Snowflake],并且目前值是 0,那麼在插入/更新時它的值将設定為雪花id值。
說明:SnowflakeAttribute 是使用者您來定義,new Snowflake().GetId() 也是由使用者您來實作
如果命名規範,可以在 aop 裡判斷,if (e.Property.Name == "createtime") e.Value = DateTime.Now;
還有。。還有很多騷操作。。不便在此展開。。。
十、展望 2020
2019 年支援了主流的資料庫:
- SqlServer 2000-2019,支援 row_number/offset fetch next 分頁自動版本選擇适配,以及其他文法的差異适配,提供 ado.net 與 odbc 兩種實作方式;
- PostgreSQL 9.4-12,完成了版本間部分差異适配,提供 ado.net 與 odbc 兩種實作方式;
- MySql 5.5、Mariadb,提供 Oracle 官方驅動、與 MySqlConnector 社群驅動,還有 odbc 實作方式;
- Oracle 11+,提供 ado.net 與 odbc 兩種實作方式;
- Sqlite,相容了 .net core / .net framework / xamarin 平台适配,支援 CodeFirst 開發模式,一個字爽!!!
- MsAccess 2003-2007,提供 oledb 實作方式,支援 CodeFirst 開發模式;
- 達夢,提供 odbc 的實作方式,并且支援 DbFirst 和 CodeFirst 兩種開發模式;
2020 年支援國産是重點,重心,重要的工作内容,南大通用将是下一個目标,并且已經在進行中了。
寫到最後面,感謝這一年來與 FreeSql 一直陪伴的兄弟朋友們。