寫在開頭
衆所周知 Dapper 是 .NET 下最輕最快的 ORM,它是喜歡寫 SQL 碼農的福音,相對于 SqlHelper 它更加友善,據統計 10個 .NETer 有 9個 用過 Dapper。
由于 .NET 環境的特殊,對 Lambda 表達式樹的喜愛,于是市面上有很多出現了基于 Dapper 的輕量級 ORM,它們幾乎都有共同特點,讓 Dapper 支援 Lambda 表達式樹,寫起來順暢如流水。
今天介紹一款本身功能已經很強大的 ORM,他提供一個隐藏得比較深的 API 功能,使用起來和 Dapper 沒什麼兩樣。
了解 Lambda 表達式樹
這要先從 Lambda 表達式開始說起,詞語中少了一個“樹”字,差别甚大。
表達式,如下各種文法糖騷操作,産生的 IL 一模一樣:
//使用C# 2.0中的匿名方法擷取字元串長度
Func<string, int> strLength = delegate(string str) { return str.Length; };
Console.WriteLine(strLength("Hello World!"));
//使用Lambda表達式
//(顯式類型參數清單)=> {語句},lambda表達式最冗長版本
strLength = (string str) => { return str.Length; };
Console.WriteLine(strLength("Hello World!"));
//單一表達式作為主體
//(顯式類型參數清單)=> 表達式
strLength = (string str) => str.Length;
Console.WriteLine(strLength("Hello World!"));
//隐式類型的參數清單
//(隐式類型參數清單)=> 表達式
strLength = (str) => str.Length;
Console.WriteLine(strLength("Hello World!"));
//單一參數的快捷文法
//參數名 => 表達式
strLength = str => str.Length;
Console.WriteLine(strLength("Hello World!"));
而表達式樹呢,代碼寫起來跟表達式差不多,如下:
Expression<Func<string, int>> strLength = str => str.Length;
表達式樹不支援代碼塊(花括号)
力求書寫簡單,一般情況我們都是這樣寫的,雖然它和表達式代碼寫起來幾乎一樣,但是傳回值和表達式不一樣,多了一個泛型 Expression<>。
表達式樹也稱表達式目錄樹,将代碼以一種抽象的方式表示成一個對象樹,樹中每個節點本身都是一個表達式。表達式樹不是可執行代碼,它是一種資料結構。它是代碼在編譯期間将編寫的代碼轉換成一個樹結構,以便後續進行逆向解析。
如上:(strLength.Body as MemberExpression).Member.Name 可以得到值 "Length"
由于表達式樹可逆向解析的特點,近十年來 EF 是一直是帶頭大哥,國産每年都要整出好幾個 ORM,大部分都是基于表達式樹解析做的。
表達式樹解析
.NET 技術文章從來不缺少表達式樹解析的這類文章,有興趣的可以百度搜尋一下,很多很多,但是想做完美可不是件簡單事。
FreeSql 在表達式樹解析上做了下足了工夫,舉例:
1、子表 in 查詢
.Where(a => fsql.Select<T>().ToList(b => b.Id).Contains(a.Id))
//WHERE a.Id in (select id from t)
2、子表 exists 查詢
.Where(a => fsql.Select<T>().Any(b => b.Id == a.Id))
//WHERE exists(select 1 from t where Id = a.Id)
3、日期格式化
ToList(a => a.CreateTime.ToString("HH:mm:ss"))
//date_format(a.`CreateTime`, '%H:%i:%s')
4、開窗函數
ToList(a => SqlExt.Rank().Over().OrderBy(a.Id).OrderByDescending(b.EdiId).ToValue())
//rank() over(order by a.Id, b.EdiId desc)
5、Join 子表
ToList(a => string.Join(",", fsql.Select<StringJoin01>().ToList(b => b.Id)))
//(SELECT group_concat(b.`Id` separator ',') FROM `StringJoin01` b)
這些特性在不同的資料庫,都需要做相應适配實作,FreeSql 還支援對導航屬性的表達式樹解析,說這些隻想證明做到細緻真的不容易。
與其自己造着麻煩,不如直接拿來主義使用?
Ado.Net 擴充實作
與其自己造着麻煩,不如直接拿來主義使用?FreeSql 提供了一種非主打的 API 使用習慣,使用起來跟 Dapper 沒什麼差別。
支援 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/達夢/神通/人大金倉/翰高/MsAccess 十多種資料庫适配,支援 Ado.net/Odbc,并且支援 .NetFramework 4.0 平台、.NET5.0、.NET Core2.1 + 平台。
第一步:以資料庫 SqlServer 通路為例,隻需要安裝已經劃分好的小包:
dotnet add packages FreeSql.Provider.SqlServer
or
Install-Package FreeSql.Provider.SqlServer
第二步:建立實體類
class TestConnectionExt {
public Guid id { get; set; }
public string title { get; set; }
public DateTime createTime { get; set; } = DateTime.Now;
}
第三步:開始 CRUD
using (var conn = new SqlConnection(connectString)) {
var list = conn.Select<TestConnectionExt>().Where(a => a.id == item.id).ToList();
}
using (var conn = new SqlConnection(connectString)) {
var item = new TestConnectionExt { title = "testinsert" };
var affrows = conn.Insert(item).ExecuteAffrows();
}
using (var conn = new SqlConnection(connectString)) {
var affrows = conn.Update<TestConnectionExt>()
.Where(a => a.Id == xxx)
.Set(a => a.title, "testupdated")
.ExecuteAffrows();
}
using (var conn = new SqlConnection(connectString)) {
var affrows = conn.Delete<TestConnectionExt>()
.Where(a => a.Id == xxx)
.ExecuteAffrows();
}
添加或更新:
using (var conn = new SqlConnection(connectString)) {
var affrows = conn.InsertOrUpdate<TestConnectionExt>()
.SetSource(item)
.ExecuteAffrows();
}
如上添加、删除、修改、查詢,已經支援實體類操作,并且支援批量插入、批量更新、批量删除、多表查詢、導航屬性查詢。
可以享用 FreeSql 幾乎所有功能。
思考:使用這種 API 貌似可以很輕松的接入到 abp vnext 中?
結束語
FreeSql 使用世界上最寬松的開源協定 MIT 托管于 github:https://github.com/dotnetcore/FreeSql 目前已釋出經曆兩年高頻率疊代的穩定版本 v2.0,歡迎關注和使用。
支援 .NetFramework 4.0+、.NetCore 2.1+、Xamarin 等支援 NetStandard 所有運作平台。
支援 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/達夢/神通/人大金倉/翰高/MsAccess 資料庫,支援 Ado.net/Odbc。
QQ群:4336577(已滿)、8578575(線上)、52508226(線上)
