Aore.SqlBuilder
1.2.3
dotnet add package Aore.SqlBuilder --version 1.2.3
NuGet\Install-Package Aore.SqlBuilder -Version 1.2.3
<PackageReference Include="Aore.SqlBuilder" Version="1.2.3" />
<PackageVersion Include="Aore.SqlBuilder" Version="1.2.3" />
<PackageReference Include="Aore.SqlBuilder" />
paket add Aore.SqlBuilder --version 1.2.3
#r "nuget: Aore.SqlBuilder, 1.2.3"
#:package Aore.SqlBuilder@1.2.3
#addin nuget:?package=Aore.SqlBuilder&version=1.2.3
#tool nuget:?package=Aore.SqlBuilder&version=1.2.3
Aore.SqlBuilder
多数据库方言 SQL 构建器核心库:把实体对象与强类型 Lambda 表达式翻译成可执行、全参数化的 SQL。纯 BCL 实现,零 NuGet 依赖,可直接配合 Dapper、手写 DbCommand 或任何 ADO.NET 执行方式使用。
using Aore.SqlBuilder;
var sql = Sql.Select<User>()
.Where(u => u.Age >= 18)
.WhereIf(!string.IsNullOrEmpty(keyword), u => u.UserName.Contains(keyword))
.OrderByDescending(u => u.CreateTime)
.Page(1, 20)
.ToSql(SqlDialect.MySql);
生成的 SQL(MySQL,值全部参数绑定):
SELECT t0.* FROM `Users` t0
WHERE ((t0.`Age` >= @p0) AND (t0.`UserName` LIKE @p1))
ORDER BY t0.`CreateTime` DESC LIMIT 20 OFFSET 0
特性一览
- 链式 API:
Sql.Select<T>()...ToSql(dialect)一行出 SQL,构建器方法全部返回自身支持链式 - 表达式树翻译:Where / 投影 / 排序 / 分组 / HAVING 全部支持强类型 Lambda,编译期可查错
- 六种内置方言:SQL Server、MySQL、Oracle、PostgreSQL、SQLite、Firebird,可注册自定义方言(如达梦)
- 全参数化防注入:所有外部值一律绑定
@p0…参数;标识符经方言引号转义;LIKE 通配符自动转义 - 防全表保护:Update / Delete 缺 WHERE 默认拒绝执行
- SqlResult 不可变产物:可跨线程共享、可重复执行、可日志打印
- 多目标框架:netstandard2.0 / net462 / net472 / net48 / net8.0 / net9.0 / net10.0
本库只负责"构建与渲染"。要把
SqlResult直接跑起来(参数转换、执行、分页、自增回读),请配套使用Aore.SqlBuilder.Extensions(基于 Dapper),或自行读取SqlResult.Sql+SqlResult.Parameters交给任意执行层。
安装
dotnet add package Aore.SqlBuilder
快速上手
1. 定义实体(约定优先,特性可选)
[SqlTable("SYS_USER", Schema = "APP")] // 表名/模式(未标注 = 类名按命名策略)
public class User
{
[SqlKey(IsIdentity = true)] // 主键 + 自增(复合主键可多处标注)
public long Id { get; set; }
[SqlColumn("USER_NAME")] // 显式列名(未标注 = 属性名按命名策略)
public string UserName { get; set; } = null!;
[SqlIgnoreUpdate] // 更新跳过(创建人只写一次)
public string? CreatedBy { get; set; }
[SqlIgnoreInsert] // 插入跳过(触发器维护)
public DateTime? RowVersion { get; set; }
[SqlNotMapped] // 不参与任何 SQL
public string? Temp { get; set; }
}
可用特性:[SqlTable] [SqlColumn] [SqlKey] [SqlIdentity] [SqlIgnoreInsert] [SqlIgnoreUpdate] [SqlNotMapped];同时兼容 DataAnnotations 的 [Table] [Column] [Key] [NotMapped](按特性全名探测)。约定:无显式主键时,Id 或 类型名+Id(int/long/Guid)自动识别为主键,int/long 视为自增。
2. 查询
// 条件 / 动态条件 / 排序 / 分页(页码从 1 起)
var query = Sql.Select<User>()
.Where(u => u.Age >= 18)
.WhereIf(minAge.HasValue, u => u.Age >= minAge!.Value)
.OrderByDescending(u => u.CreateTime)
.Page(1, 20)
.ToSql(SqlDialect.MySql);
// 投影:匿名对象自动 AS 别名(开启 SnakeCase 后列名 user_name,别名保证映射)
var proj = Sql.Select<User>()
.Select(u => new { u.Id, u.UserName })
.ToSql(SqlDialect.MySql);
// 函数 / 三目 / ?? 混用,函数经 SqlF
var agg = Sql.Select<Order>()
.Select(o => new
{
UserId = o.UserId,
Cnt = SqlF.Count(o.Id),
Total = SqlF.Sum(o.Amount),
Level = o.Amount >= 100 ? "大额" : "小额",
})
.GroupBy(o => o.UserId)
.Having(o => SqlF.Count(o.Id) > 1)
.ToSql(SqlDialect.MySql);
// JOIN + 跨实体投影(跨实体 lambda 参数须显式标类型)
var joined = Sql.Select<User>()
.Join<Order>((u, o) => u.Id == o.UserId) // INNER JOIN;另有 LeftJoin/RightJoin/FullJoin/CrossJoin
.Where((User u, Order o) => o.Amount > 50)
.Select((User u, Order o) => new { u.UserName, o.Amount })
.ToSql(SqlDialect.MySql);
// IN / IN 子查询 / BETWEEN / EXISTS
var adv = Sql.Select<User>()
.Where(u => ids.Contains(u.Id)) // IN (@p0, @p1, …)
.WhereIn(u => u.Id, Sql.Select<Order>().Select(o => o.UserId)) // IN (SELECT …)
.WhereBetween(u => u.Age, 18, 30)
.Where(u => Sql.Exists(Sql.Select<Order>().Where(o => o.UserId == u.Id)))
.ToSql(SqlDialect.MySql);
3. 插入
// 单实体:INSERT INTO `Users` (`UserName`, `Age`, …) VALUES (@p0, @p1, …)
var insert = Sql.Insert(user).ToSql(SqlDialect.MySql);
// 忽略 null 列(交给数据库默认值)/ 限定插入列 / 显式写自增列
var flex = Sql.Insert(user).IgnoreNullColumns().Columns(u => new { u.UserName, u.Age }).ToSql(SqlDialect.MySql);
// 回读自增主键(MySQL 追加 SELECT LAST_INSERT_ID();PG RETURNING;Oracle RETURNING INTO 输出参数)
var withId = Sql.Insert(user).ReturnId().ToSql(SqlDialect.MySql);
// 批量插入:多行 VALUES,按参数上限(MaxParameters,默认 2000)自动分片
var batch = Sql.InsertRange(userList).IgnoreNullColumns().ToSql(SqlDialect.MySql);
IReadOnlyList<SqlResult> statements = Sql.InsertRange(userList).ToSqlBatch(SqlDialect.MySql);
4. 更新(缺 WHERE 默认拒绝执行)
// 表达式精确修改(推荐):SET `LoginCount` = @p0, `Balance` = (`Balance` - @p1)
var update = Sql.Update<User>()
.Set(u => u.LoginCount, 1)
.SetExpr(u => u.Balance, u => u.Balance - 100)
.Increment(u => u.LoginCount, 1) // 数据库端自增,并发安全
.Where(u => u.Id == id)
.ToSql(SqlDialect.MySql);
// 实体全字段更新(跳过主键/自增/IgnoreUpdate/null)+ 主键定位
var byEntity = Sql.UpdateBy(user).ToSql(SqlDialect.MySql);
// 确需全表更新时显式放行
var all = Sql.Update<User>().Set(u => u.IsActive, true).AllowFullTable().ToSql(SqlDialect.MySql);
5. 删除
var delete = Sql.Delete<User>().Where(u => u.LastLogin < cutoff).ToSql(SqlDialect.MySql);
var byKey = Sql.Delete<User>().WhereKeyOf(user).ToSql(SqlDialect.MySql); // 按主键定位
// 同样缺 WHERE 拒绝执行;AllowFullTable() 显式放行
6. Upsert(渲染由方言决定)
var upsert = Sql.Upsert(user) // 冲突列默认 = 主键
.OnKey(u => new { u.UserName }) // 或指定唯一索引列
.IgnoreNullColumns() // null 列不插入、冲突时不覆盖
.UpdateColumns(u => new { u.Age }) // 冲突时更新的列(默认全部非键列)
.ToSql(SqlDialect.MySql);
// MySQL → ON DUPLICATE KEY UPDATE;PG/SQLite → ON CONFLICT DO UPDATE;
// SQL Server / Oracle / Firebird → MERGE
7. 插入自查询
var source = Sql.Select<User>().Where(u => u.Age >= 26);
var archive = Sql.InsertFrom<Archive, User>(source)
.Columns(a => new { a.UserName, a.Age }) // 目标列
.FromColumns(u => new { u.UserName, u.Age }) // 源列(一一对应、顺序一致)
.ToSql(SqlDialect.MySql);
入口类 Sql 全量方法
| 方法 | 用途 |
|---|---|
Sql.Select<T>() |
SELECT 构建器 |
Sql.Insert(entity) / Sql.InsertRange(list) |
单实体 / 批量插入(批量自动分片) |
Sql.Update<T>() / Sql.UpdateBy(entity) |
更新构建器 / 快捷全字段更新 + 主键 WHERE |
Sql.Delete<T>() |
删除构建器(缺 WHERE 拒绝执行) |
Sql.Upsert(entity) |
存在则更新 / 不存在则插入 |
Sql.InsertFrom<T, TSource>(source) |
INSERT INTO … SELECT |
Sql.Raw(sql, params) |
原生片段:@name 占位自动换方言前缀并绑定参数;严禁把用户输入直接拼进 sql 文本 |
Sql.Truncate<T>(dialect) |
TRUNCATE TABLE(SQLite 自动降级 DELETE FROM) |
Sql.Batch(dialect, builders[]) |
多语句合并:同一上下文渲染(参数连续编号、别名不冲突),以 ;\n 连接 |
Sql.ExistsOf(builder, dialect) |
生成 SELECT CASE WHEN EXISTS (…) THEN 1 ELSE 0 END 探测语句 |
Sql.Exists(sub) / Sql.ExistsNot(sub) |
仅限在 Where/投影表达式树内使用(直接调用抛 InvalidOperationException) |
查询构建器 SelectBuilder 常用 API
| 分类 | 方法 |
|---|---|
| 投影 | Select() 全列 / Select(u => …) 单列或匿名对象 / Select(Sql.Raw(…), "alias") 原生片段 / Select(sub, "alias") 子查询 / 跨实体投影(JOIN 后) |
| 条件 | Where / WhereIf / WhereIn / WhereNotIn / WhereBetween / WhereExists / WhereExistsNot / WhereRaw / WhereRawIf |
| JOIN | Join / LeftJoin / RightJoin / FullJoin(MySQL 不支持)/ CrossJoin / JOIN 子查询(别名必填)/ WithNoLock(仅 SQL Server,其他方言忽略) |
| 分组 | GroupBy(匿名对象 = 多列)/ Having / HavingRaw / HavingBetween / HavingNull / HavingNotNull / HavingTrue / HavingFalse |
| 排序分页 | OrderBy / OrderByDescending / OrderByRaw / OrderByRandom / Page(页码, 页大小) / Limit / Offset / Distinct |
| CTE | With("名称", 子查询) / WithRaw / WithValues(Ad-hoc VALUES 表)/ Table("名称") 引用 CTE |
| 集合操作 | Union / UnionAll / Except(All) / Intersect(All) / UnionRaw,之后再 OrderBy/Page 作用于合并结果 |
| 杂项 | Table("表名", "模式") 覆盖表名(分表场景) |
| 输出 | ToSql(dialect) → SqlResult;ToCountSql(dialect) → 自动剥离排序分页的 Count 语句 |
SqlF · SQL 函数(仅限表达式树内使用)
SqlF.Count() SqlF.Count(x) SqlF.Sum SqlF.Avg SqlF.Min SqlF.Max SqlF.Length SqlF.Upper SqlF.Lower SqlF.Trim SqlF.SubString(0 基自动换 1 基)SqlF.Replace SqlF.Concat SqlF.Coalesce SqlF.NullIf SqlF.Abs SqlF.Round SqlF.Now —— 具体函数名由方言翻译(如 Now() 在 SQL Server 渲染 GETDATE()、Oracle 渲染 SYSDATE)。
表达式翻译支持(节选)
| C# 写法 | SQL 形态 |
|---|---|
u.Age > 18、u.Name == "x" |
(t0.Age > @p0)、(t0.Name = @p0) |
u.CreatedAt == null / != null |
IS NULL / IS NOT NULL |
u.IsActive(bool 列) |
(t0.IsActive = 1)(PG:TRUE) |
!cond / a && b / a \|\| b |
NOT (…) / (… AND …) / (… OR …) |
u.Nick ?? u.Name |
COALESCE(…) |
cond ? x : y |
CASE WHEN … THEN … ELSE … END(嵌套自动扁平化) |
s.Contains("x") / StartsWith / EndsWith |
LIKE '%x%' / 'x%' / '%x'(通配符转义) |
s.ToUpper() / ToLower() / Trim() / Replace() / Substring() / s.Length |
UPPER / LOWER / TRIM / REPLACE / SUBSTRING(0 基→1 基)/ LEN·CHAR_LENGTH·LENGTH |
string.IsNullOrEmpty(s) |
(s IS NULL OR s = '') |
ids.Contains(u.Id) |
IN (@p…);空集合 → (1 = 0) 防全表 |
| 枚举比较 | 底层数值绑定 |
| 闭包变量 / 静态属性 / 本地方法结果 | 客户端求值后绑定参数 |
不支持的表达式(自由表达式、未映射属性)构建期抛 SqlBuilderException 并给出修复指引;可改用 WhereRaw 或先在客户端求值。
SqlResult · 构建产物
ToSql() 返回不可变的 SqlResult,可跨线程共享、重复执行:
| 成员 | 说明 |
|---|---|
Sql |
SQL 文本(含方言引号与参数前缀);ToString() 即返回它,调试打印友好 |
Parameters |
IReadOnlyList<SqlParameter>(Name 不含前缀 / Value / IsOutput) |
Dialect |
生成时所用方言 |
HasOutputParameters |
是否含输出参数(Oracle RETURNING INTO) |
ToDictionary() |
参数字典(不含输出参数),可直接传 Dapper |
全局配置 SqlOptions
// 进程级默认:仅启动期调用一次(Default 是只读快照,直接修改会抛异常)
SqlOptions.SetDefault(new SqlOptions
{
NamingPolicy = SqlNamingPolicy.SnakeCase, // AsIs(默认)/SnakeCase/CamelCase/Lowercase/Uppercase
IdentifierQuoting = SqlIdentifierQuoting.All, // 标识符引号开关:None/Table/Column/All(默认)
EscapeLikePattern = true, // LIKE 通配符转义(默认开)
MaxParameters = 2000, // 批量插入分片阈值(SQL Server 上限 2100 留余量)
ParameterBaseName = "p", // 参数名前缀:p0、p1…
UseLegacyOraclePaging = false, // true = Oracle 11g ROWNUM 分页
});
// 局部覆盖(不影响全局)
var sql = Sql.Select<User>().ToSql(SqlDialect.PostgreSql,
new SqlOptions { NamingPolicy = SqlNamingPolicy.SnakeCase });
IdentifierQuoting 决定表名/列名渲染时是否加方言引号(引号字符始终由方言决定):Oracle 库建表未加引号时报 ORA-00942/ORA-00904,可设 None 让大小写折叠交还数据库。注意:投影别名、子查询/CTE 名始终加引号;开关不检测保留字(Order/User 等 Table/Class 名裸出会语法错误,须保持 All);裸出前做标识符形状校验。
方言
// 内置实例:SqlServer / MySql / Oracle / PostgreSql / Sqlite / Firebird
var sql = Sql.Select<User>().ToSql(SqlDialect.SqlServer);
// 按名称获取(大小写不敏感,支持别名 mssql / mariadb / postgres / pg / sqlite3 / fb)
var dialect = SqlDialect.Get("pg");
// 自定义方言:继承 DialectBase,最少覆写 Name / ParameterPrefix / QuoteIdentifier / GetFunctionName
public class DmDialect : DialectBase
{
public override string Name => "DM";
public override string ParameterPrefix => "?";
public override string QuoteIdentifier(string name) => "\"" + name.Replace("\"", "\"\"") + "\"";
public override string GetFunctionName(SqlFunction function) => DefaultFunctionName(function);
}
SqlDialect.Register(new DmDialect());
var dmSql = Sql.Select<User>().ToSql(SqlDialect.Get("DM"));
架构约束:构建器层不写任何方言特判,所有数据库差异收敛在 ISqlDialect / DialectBase —— 分层为 构建意图(Builders)→ 渲染(Dialects)→ 执行(Extensions)。
异常与安全
- 构建期错误一律抛
Aore.SqlBuilder.Exceptions.SqlBuilderException,消息可直接指导修复(缺 WHERE、未映射属性、空 IN 集合含 null、批量 ReturnId 不支持等) SqlF.*/Sql.Exists脱离表达式树直接调用抛InvalidOperationException- 安全红线:所有外部值一律参数绑定(
@p0…),标识符经方言转义;Sql.Raw的值必须走参数绑定,严禁字符串拼接用户输入进 SQL
NativeAOT 边界:依赖表达式编译与反射元数据发现,不保证 NativeAOT;裁剪发布需自行保留实体类型。
相关包
- Aore.SqlBuilder.Extensions:Dapper 执行层扩展(参数转换、同步/异步执行、分页自动 Count、自增回读、事务、批量)
- 更多文档见源码仓库
docs/目录(03-查询构建、04-增删改与Upsert、05-多数据库方言、07-实体映射与特性、12-方法使用手册等)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 is compatible. net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.6.2
- System.ValueTuple (>= 4.5.0)
-
.NETFramework 4.7.2
- No dependencies.
-
.NETFramework 4.8
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Aore.SqlBuilder:
| Package | Downloads |
|---|---|
|
Aore.SqlBuilder.Extensions
Aore.SqlBuilder 的 Dapper 执行扩展:参数转换、同步/异步执行、分页查询(自动 Count)、通用 CRUD 执行器。 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2.3 | 49 | 9/24/2026 |