簡介
在使用 SqlKata 構建 SQL 時,雖然其鏈式 API 強大靈活,但仍需通過字符串或匿名字段進行表與列的映射,缺乏對實體類型和字段的靜態檢查。FluentSqlKata 基於 SqlKata,提供了一套基於表達式的強類型查詢構建能力,能夠:
- 通過
Lambda表達式指定實體與列,更安全、可重構 - 保留
SqlKata的所有特性與多數據庫編譯器支持 - 在運行時動態構造
ORDER BY時,支持別名排序等高級功能
支持環境與安裝
- 目標框架:
.NET Standard 2.0,兼容.NET Framework 4.6.1+、.NET Core 2.0+、.NET 5/6/7/8/9/10等 - 安裝命令:
dotnet add package FluentSqlKata --version 1.1.7
核心功能
強類型查詢
使用 FluentQuery.Query() 開始,From(() => alias) 指定實體別名,後續所有列、條件、排序均通過表達式指定:
var query = FluentQuery.Query()
.From(() => myCust)
.Select(() => result.CustomerId, () => myCust.Id)
.Where(q => q.Where(() => myCust.Name, "John"))
.OrderByColumn(() => myCust.Name);
動態別名排序
對於 SelectRaw 或計算列,支持 OrderByAlias(() => aliasProp),自動以別名生成 ORDER BY:
.SelectRaw(() => model.Name, "ISNULL({0}, 'Unknown')", () => myCust.Name)
.OrderByAlias(() => model.Name);
聯表與聚合
支持多種 Join 重載,包括表達式構造的 ON 條件;GroupBy、SelectCount 等聚合 API 與 SqlKata 保持一致:
.Join(() => myCont, () => myCont.CustomerId, () => myCust.Id)
.SelectCount(() => result.Count, () => myCont.Id)
.GroupBy(() => myCust.Name);
API 詳解
| 方法 | 説明 |
|---|---|
FluentQuery.Query() |
創建一個新的強類型查詢構建器 |
.From(() => alias) |
指定根表實體及其別名 |
.Select(exprAlias, exprColumn) |
添加列映射,並指定結果字段 |
.Where(Func<Query, Query>) |
通過內部 SqlKata API 構造過濾條件 |
.Join(...) |
多種重載,可按表達式或列映射方式構造聯表 |
.SelectRaw(alias, fmt, expr…) |
原生 SQL 片段映射,同時支持別名排序 |
.SelectCount(alias, expr) |
生成 COUNT(...) AS alias 聚合列 |
.GroupBy(expr…) |
指定分組字段 |
.OrderByColumn(expr) |
按指定列排序 |
.OrderByAlias(expr) |
按先前定義的列別名排序 |
.Compile(compiler) |
使用指定編譯器生成最終 SQL 與參數 |
用法示例
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public DateTime LastUpdated { get; set; }
}
基本查詢
使用 FluentQuery 構建類型安全的 SELECT 查詢:
using FluentSqlKata;
using SqlKata;
using SqlKata.Execution;
using System.Data.SqlClient;
public async Task Main()
{
using var connection = new SqlConnection("Server=localhost;Database=testdb;Trusted_Connection=True;");
var compiler = new SqlServerCompiler();
var db = new QueryFactory(connection, compiler);
Customer myCust = null;
(string CustomerId, string CustomerName) result = default;
var query = FluentQuery.Query()
.From(() => myCust)
.Select(() => result.CustomerId, () => myCust.Id)
.Select(() => result.CustomerName, () => myCust.Name);
var customers = await db.FromQuery(query).GetAsync<(string, string)>();
foreach (var customer in customers)
{
Console.WriteLine($"ID: {customer.Item1}, Name: {customer.Item2}");
}
}
條件查詢
使用類型安全的 Where 方法:
Customer myCust = null;
(string CustomerId, string CustomerName) result = default;
var query = FluentQuery.Query()
.From(() => myCust)
.Select(() => result.CustomerId, () => myCust.Id)
.Select(() => result.CustomerName, () => myCust.Name)
.Where(q => q.Where(() => myCust.Name, "John")
.OrWhereContains(() => myCust.Name, "oh"));
var query_str = new SqlServerCompiler().Compile(query).ToString();
Console.WriteLine(query_str);
連接(JOIN)
類型安全的 JOIN 查詢:
Contact myCont = null;
Customer myCust = null;
(string FirstName, string LastName, string CustomerId, string CustomerName) result = default;
var query = FluentQuery.Query()
.From(() => myCont)
.Join(() => myCust, () => myCust.Id, () => myCont.CustomerId)
.Select(() => result.FirstName, () => myCont.FirstName)
.Select(() => result.LastName, () => myCont.LastName)
.Select(() => result.CustomerId, () => myCont.CustomerId)
.Select(() => result.CustomerName, () => myCust.Name);
var query_str = new SqlServerCompiler().Compile(query).ToString();
Console.WriteLine(query_str);
優缺點
優點
- 類型安全:通過表達式引用表和列,編譯器捕獲拼寫錯誤。
- 靈活性:繼承
SqlKata的複雜查詢支持(子查詢、JOIN、CTE)。 - 高性能:結合
Dapper,性能接近原生ADO.NET。 - 跨數據庫支持:通過
SqlKata的編譯器適配多種數據庫。
缺點
- 學習曲線:表達式語法(如
() => myCust.Name)比SqlKata的字符串語法複雜。 - 部分功能依賴字符串:插入、更新和刪除仍使用
SqlKata的字符串-based API。 - 文檔有限:
FluentSqlKata的文檔較少,需參考SqlKata文檔和GitHub示例。 - 依賴
SqlKata:增加了依賴項,需熟悉SqlKata的核心概念。
示例項目
using Microsoft.AspNetCore.Mvc;
using FluentSqlKata;
using SqlKata;
using SqlKata.Execution;
using System.Threading.Tasks;
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public DateTime LastUpdated { get; set; }
}
[ApiController]
[Route("api/customers")]
public class CustomersController : ControllerBase
{
private readonly QueryFactory _db;
public CustomersController(QueryFactory db)
{
_db = db;
}
[HttpGet]
public async Task<IActionResult> Search([FromQuery] string searchText)
{
Customer myCust = null;
(string CustomerId, string CustomerName) result = default;
var query = FluentQuery.Query()
.From(() => myCust)
.Select(() => result.CustomerId, () => myCust.Id)
.Select(() => result.CustomerName, () => myCust.Name);
if (!string.IsNullOrEmpty(searchText))
{
query.Where(q => q.WhereContains(() => myCust.Name, searchText));
}
var customers = await _db.FromQuery(query).GetAsync<(string, string)>();
return Ok(customers);
}
}
啓動配置:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<QueryFactory>(sp =>
{
var connection = new SqlConnection("Server=localhost;Database=testdb;Trusted_Connection=True;");
var compiler = new SqlServerCompiler();
return new QueryFactory(connection, compiler);
});
}
}