LinqSharp 是個開源 LINQ 擴充套件庫,它允許您編寫簡單程式碼來生成複雜查詢,包括查詢擴充套件和動態查詢生成。
LinqSharp.EFCore 是對 EntityFramework 的增強庫,提供更多資料註解、資料庫函式及自定義儲存規則等。
https://github.com/zmjack/LinqSharp
由於內容較多,將分篇介紹公開內容、原理及案例分享:
LinqSharp:簡化複雜查詢LinqSharp:動態構建 LINQ 查詢LinqSharp.EFCore:表設計資料註解LinqSharp.EFCore:欄位標準化資料註解LinqSharp.EFCore:函式對映LinqSharp.EFCore:列式儲存代理LinqSharp.EFCore:關聯計算與審計本文多數示例,提供線上執行測試(.NET Fiddle)。
LinqSharp 為 LINQ 提供了以下增強(“記憶體查詢”或“SQL生成”):
預設返回方法MinOrDefault:提供預設返回的 Min 方法;MaxOrDefault:提供預設返回的 Max 方法;AverageOrDefault:提供預設返回的 Average 方法。查詢值最小或最大的記錄WhereMin:查詢指定欄位最小的記錄;WhereMax:查詢指定欄位最大的記錄。資料搜尋Search:在指定欄位或連結表字段中模糊或精確查詢資料;分頁查詢SelectPage:查詢結果分頁或執行分頁查詢。序列排序OrderByCase / ThenByCase:按指定字串序列排序。構建動態查詢XWhere:構建動態查詢。以下方法僅提供於記憶體查詢:
按組元素數量分組GroupByCount:按分組記錄元素數量分組。樹結構查詢SelectMore:按樹結構遍歷,選擇“所有樹節點中滿足條件的節點”;SelectUntil:按樹結構遍歷,直到在每個子路徑中找到滿足條件的節點,選擇該節點;SelectWhile:按樹結構遍歷,選擇“所有子路徑中連續滿足條件的路徑節點”。示例庫 Northwnd
Northwnd 是 SQL Server 早期附帶的示例資料庫,該資料庫描述“公司銷售產品網”簡單案例場景。包括“僱員(Employees)”“產品訂單(Orders)”“供應商(Suppliers)”的關係網路。
本文示例使用的是它的 Sqlite 版本(Code First):
https://github.com/zmjack/Northwnd
透過 NuGet 安裝:
dotnet add package Northwnddotnet add package LinqSharpdotnet add package LinqSharp.EFCore
簡單使用:
using (var sqlite = NorthwndContext.UseSqliteResource()){ ...}
輸出 SQL
“輸出 SQL”是研究“SQL 生成”的基礎,使用 LinqSharp.EFCore 中的 ToSql 方法:
(線上示例:ToSql | C# Online Compiler)
using (var sqlite = NorthwndContext.UseSqliteResource()){ var query = sqlite.Regions .Where(x => x.RegionDescription == "Northern"); var sql = query.ToSql();}
生成 SQL:
SELECT "r"."RegionID", "r"."RegionDescription"FROM "Regions" AS "r"WHERE "r"."RegionDescription" = 'Northern';
注1:由於不同版本的 EntityFrameworkCore 的 SQL 生成器設計不同,因此,生成 SQL 可能會存在差異。(EntityFrameworkCore 5.0 公開了 ToQueryString 來支援這項功能)。
注2:LinqSharp.EFCore 最新版本不相容所有 EntityFrameworkCore,需使用“大版本號”與 EntityFrameworkCore 一致的發行庫(例如,2.2.x,3.0.x,3.1.x)。
預設返回方法擴充套件
MinOrDefault:原函式 Min 的不拋異常版本,異常返回預設值;MaxOrDefault:原函式 Max 的不拋異常版本,異常返回預設值;AverageOrDefault:原函式 Average 的不拋異常版本,異常返回預設值。(線上示例:MinOrDefault | C# Online Compiler)
// throw 'Sequence contains no elements'new int[0].Min();new int[0].MinOrDefault(); // 0new int[0].MinOrDefault(-1); // -1
查詢值最小或最大的記錄
WhereMin:查詢指定欄位最小的記錄;WhereMax:查詢指定欄位最大的記錄。WhereMin 和 WhereMax 會進行兩次查詢:
查詢指定欄位的“最小值”或“最大值”;查詢指定欄位“最小值”或“最大值”的記錄。例如,查詢員工(Empolyees)表中年齡最小的員工:
(線上示例:WhereMax | C# Online Compiler)
var query = sqlite.Employees .WhereMax(x => x.BirthDate);var result = query.Select(x => new{ x.EmployeeID, x.FirstName, x.BirthDate,}).ToArray();
生成 SQL:
/* Step 1 */SELECT MIN("e"."BirthDate")FROM "Employees" AS "e";/* Step 2 */SELECT *FROM "Employees" AS "e"WHERE "e"."BirthDate" = '1966-01-27 00:00:00';
執行結果:
資料搜尋
Search:返回“從指定欄位或外來鍵表字段中進行模糊或精確查詢”的查詢結果。Search 函式提供了四種搜尋模式(SearchOption):
Contains(預設):任何指定欄位中“包含”搜尋字串;NotContains:所有指定欄位中都“不包含”搜尋字串;Equals:搜尋字串與某指定欄位“相等”;NotEquals:搜尋字串“不在”任何指定欄位中。例如,查詢僱員(Employees)表中地址(Address)或城市(City)包含字母 m 的僱員:
(線上示例:Search | C# Online Compiler)
var query = sqlite.Employees .Search("m", e => new { e.Address, e.City, });var result = query.Select(x => new{ x.EmployeeID, x.Address, x.City,}).ToArray();
生成 SQL:
SELECT *FROM "Employees" AS "e"WHERE (('m' = '') OR (instr("e"."Address", 'm') > 0)) OR (('m' = '') OR (instr("e"."City", 'm') > 0));
執行結果:
Search 函式同樣提供了外連結串列的查詢(主表或從表查詢)。
例如,查詢供應商(Suppliers)表中供應任何種類豆腐(Tofu)的供應商:
(線上示例:Search (Details) | C# Online Compiler)
var query = sqlite.Suppliers .Include(x => x.Products) .Search("Tofu", s => new { ProductNames = s.Products.Select(x => x.ProductName), });var result = query.Select(x => new{ x.SupplierID, x.CompanyName, Products = string.Join(", ", x.Products.Select(p => p.ProductName)),}).ToArray();
生成 SQL:
SELECT *FROM "Suppliers" AS "s"LEFT JOIN "Products" AS "p" ON "s"."SupplierID" = "p"."SupplierID"WHERE EXISTS ( SELECT 1 FROM "Products" AS "p0" WHERE ("s"."SupplierID" = "p0"."SupplierID") AND (('Tofu' = '') OR (instr("p0"."ProductName", 'Tofu') > 0)))ORDER BY "s"."SupplierID", "p"."ProductID";
執行結果:
分頁查詢
SelectPage:查詢結果分頁或執行分頁查詢。(分頁引數從第 1 頁開始)例如,查詢僱員(Employees)表,按每頁 2 條記錄分頁,選擇第 3 頁的記錄返回:
(線上示例:SelectPage | C# Online Compiler)
var query = sqlite.Employees .SelectPage(pageNumber: 3, pageSize: 2);var result = query.Select(x => new{ x.EmployeeID, x.Address, x.City,}).ToArray();
生成 SQL:
SELECT *FROM "Employees" AS "e"ORDER BY (SELECT 1)LIMIT 2 OFFSET 4;
執行結果:
序列排序
OrderByCase / ThenByCase:按指定字串序列排序。例如,查詢地區(Regions)表,將結果按 N / E / W / S 的地區序列排序返回:
(線上示例:OrderByCase | C# Online Compiler)
var query = sqlite.Regions .OrderByCase(x => x.RegionDescription, new[] { "Northern", "Eastern", "Western", "Southern", });var result = query.Select(x => new{ x.RegionID, x.RegionDescription,});
執行 SQL:
SELECT *FROM "Regions" AS "r"ORDER BY CASE WHEN "r"."RegionDescription" = 'Northern' THEN 0 ELSE CASE WHEN "r"."RegionDescription" = 'Eastern' THEN 1 ELSE CASE WHEN "r"."RegionDescription" = 'Western' THEN 2 ELSE CASE WHEN "r"."RegionDescription" = 'Southern' THEN 3 ELSE 4 END END ENDEND;
執行結果:
按組元素數量分組
數量分組函式 GroupByCount 用於根據指定每組記錄數量(每組最多允許 n 條記錄)進行特殊分組。
例如,將如下指定字串按每行 16 個字元分成多行:
var s = "0123456789ABCDEF0123456789ABCDEF" .GroupByCount(16) .Select(g => new string(g.ToArray()));
0123456789ABCDEF0123456789ABCDEF
樹結構查詢
SelectMore:按樹結構遍歷,選擇“樹節點中 所有 滿足條件的 節點”;SelectUntil:按樹結構遍歷,直到 在每個子路徑中找到滿足條件的節點,選擇 該節點;SelectWhile:按樹結構遍歷,選擇“所有子路徑 中連續滿足條件的 路徑節點”。例如,僱員(Employees)表按照 EmployeeID 和 ReportsTo 定義結構如下:
SelectMore
按樹結構遍歷,選擇“樹節點中 所有 滿足條件的 節點”。
例如,查詢由 2 號僱員 Andrew 領導的所有成員(2, 1, 3, 4, 5, 6, 7, 9, 8):
方法:使用 SelectMore 從根節點查詢即可。
(線上示例:SelectMore | C# Online Compiler)
var employees = sqlite.Employees .Include(x => x.Superordinate) .Include(x => x.Subordinates) .ToArray();var query = employees .Where(x => x.EmployeeID == 2) .SelectMore(x => x.Subordinates);var result = query.Select(x => new{ x.EmployeeID, x.FirstName, x.ReportsTo, ReportsTo_ = x.Superordinate?.FirstName,});
執行結果:
SelectUntil
按樹結構遍歷,直到 在每個子路徑中找到滿足條件的節點,選擇 該節點。
例如,查詢由 2 號僱員 Andrew 領導的所有基層員工(葉節點,1, 3, 6, 7, 9, 8):
方法:使用 SelectUntil 從根節點查詢,直到節點 Subordinates 為空。
(線上示例:SelectUntil | C# Online Compiler)
var employees = sqlite.Employees .Include(x => x.Superordinate) .Include(x => x.Subordinates) .ToArray();var query = employees .Where(x => x.EmployeeID == 2) .SelectUntil(x => x.Subordinates, x => !x.Subordinates.Any());var result = query.Select(x => new { x.EmployeeID, x.FirstName, x.ReportsTo, ReportsTo_ = x.Superordinate?.FirstName,});
執行結果:
SelectWhile
按樹結構遍歷,選擇“所有子路徑 中連續滿足條件的 路徑節點”。
例如,查詢由 2 號僱員 Andrew 領導的所有非基層員工(非葉節點,2, 5):
方法:使用 SelectWhile 從根節點查詢路徑,直到節點 Subordinates 為空。
(線上示例:SelectWhile | C# Online Compiler)
var employees = sqlite.Employees .Include(x => x.Superordinate) .Include(x => x.Subordinates) .ToArray();var query = employees .Where(x => x.EmployeeID == 2) .SelectWhile(x => x.Subordinates, x => x.Subordinates.Any());var result = query.Select(x => new { x.EmployeeID, x.FirstName, Subordinates = string.Join(", ", x.Subordinates .SelectMore(s => s.Subordinates) .Select(s => s.FirstName)),});
執行結果:
原文:https://mp.weixinbridge.com/mp/wapredirect?url=https%3A%2F%2Fgithub.com%2Fzmjack%2FLinqSharp%2Fblob%2Fmaster%2FREADME-CN.md