Sqlx 0.6.9

dotnet add package Sqlx --version 0.6.9
                    
NuGet\Install-Package Sqlx -Version 0.6.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sqlx" Version="0.6.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sqlx" Version="0.6.9" />
                    
Directory.Packages.props
<PackageReference Include="Sqlx" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sqlx --version 0.6.9
                    
#r "nuget: Sqlx, 0.6.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sqlx@0.6.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sqlx&version=0.6.9
                    
Install as a Cake Addin
#tool nuget:?package=Sqlx&version=0.6.9
                    
Install as a Cake Tool

Sqlx

NuGet License .NET LTS Tests Coverage AOT Performance

高性能、AOT 友好的 .NET 数据库访问库。使用源生成器在编译时生成代码,零运行时反射,完全支持 Native AOT。

核心特性

  • 🚀 高性能 - 优化的 ResultReader,某些场景比 Dapper.AOT 快 5.8%,最低 GC 压力
  • ⚡ 零反射 - 编译时源生成,运行时无反射开销
  • 🎯 类型安全 - 编译时验证 SQL 模板和表达式
  • 🌐 多数据库 - SQLite、PostgreSQL、MySQL、SQL Server、Oracle、DB2
  • 📦 AOT 就绪 - 完全支持 Native AOT,通过 3124 个单元测试
  • 🔧 LINQ 支持 - IQueryable 接口,支持 Where/Select/OrderBy/Join 等
  • 💾 智能缓存 - SqlQuery<T> 泛型缓存,自动注册 EntityProvider
  • 🔍 自动发现 - 源生成器自动发现 SqlQuery<T> 和 SqlTemplate 中的实体类型
  • ✨ 智能优化 - 自动选择最优 ResultReader 策略,零配置

快速开始

dotnet add package Sqlx
// 1. 定义实体(支持 class、record、struct)
[Sqlx, TableName("users")]
public class User
{
    [Key] public long Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

// 也支持 record 类型
[Sqlx, TableName("users")]
public record UserRecord(long Id, string Name, int Age);

// 也支持 struct 类型
[Sqlx, TableName("users")]
public struct UserStruct
{
    [Key] public long Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

// 2. 定义仓储接口
public interface IUserRepository : ICrudRepository<User, long>
{
    [SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE age >= @minAge")]
    Task<List<User>> GetAdultsAsync(int minAge);
    
    // 输出参数支持(仅同步方法,C#不支持异步方法使用out/ref)
    [SqlTemplate("INSERT INTO {{table}} (name, age) VALUES (@name, @age)")]
    int InsertAndGetId(string name, int age, [OutputParameter(DbType.Int32)] out int id);
}

// 3. 实现仓储(代码自动生成)
[SqlDefine(SqlDefineTypes.SQLite)]
[RepositoryFor(typeof(IUserRepository))]
public partial class UserRepository(DbConnection connection) : IUserRepository { }

// 4. 使用
await using var conn = new SqliteConnection("Data Source=app.db");
var repo = new UserRepository(conn);
var adults = await repo.GetAdultsAsync(18);

// 使用输出参数(同步方法)
var result = repo.InsertAndGetId("John", 25, out int newId);
Console.WriteLine($"Inserted ID: {newId}");

重要说明: Sqlx 中有两个不同的 SqlTemplate

  • [SqlTemplate] 特性 (Sqlx.Annotations) - 用于标注接口方法,定义 SQL 模板
  • SqlTemplate (Sqlx) - 运行时类,用于调试查看生成的 SQL
using Sqlx;                    // SqlTemplate 类
using Sqlx.Annotations;        // [SqlTemplate] 特性

public interface IUserRepository
{
    // [SqlTemplate] 特性 - 标注方法执行查询
    [SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE id = @id")]
    Task<User?> GetByIdAsync(long id);
    
    // SqlTemplate 类 - 返回类型用于调试(不执行查询)
    [SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE id = @id")]
    SqlTemplate GetByIdSql(long id);
}

// 调试使用
var template = repo.GetByIdSql(123);
Console.WriteLine($"SQL: {template.Sql}");

SQL 模板占位符

占位符自动适配不同数据库方言:

占位符 说明 示例输出
{{table}} 表名(带方言引号) "users" (PostgreSQL)
{{columns}} 所有列名 id, name, age
{{columns --exclude Id}} 排除指定列 name, age
{{values --exclude Id}} 参数占位符 @name, @age
{{values --inline CreatedAt=CURRENT_TIMESTAMP}} 内联表达式(INSERT 默认值) @name, @age, CURRENT_TIMESTAMP
{{set --exclude Id}} UPDATE SET 子句 name = @name
{{set --inline Version=Version+1}} 内联表达式(UPDATE 计算字段) name = @name, version = version+1
{{where --object filter}} 对象条件查询 (name = @name AND age = @age)
{{if notnull=param}}...{{/if}} 条件包含 动态 SQL
{{var --name varName}} 运行时变量(SqlxVar) tenant-123 (字面量)

SqlxVar - 声明式变量提供器

使用 [SqlxVar] 特性声明运行时变量,自动生成代码,零反射,AOT 兼容:

public partial class UserRepository
{
    // 标记方法为变量提供器
    [SqlxVar("tenantId")]
    private string GetTenantId() => TenantContext.Current;
    
    [SqlxVar("userId")]
    private string GetUserId() => UserContext.CurrentUserId.ToString();
    
    [SqlxVar("timestamp")]
    private static string GetTimestamp() => DateTime.UtcNow.ToString("O");
    
    // 源生成器自动创建 GetVar 方法和 VarProvider 属性
}

// 在 SQL 模板中使用
[SqlTemplate(@"
    SELECT {{columns}} FROM {{table}} 
    WHERE tenant_id = {{var --name tenantId}} 
    AND user_id = {{var --name userId}}
")]
Task<List<User>> GetMyDataAsync();

// 生成的 SQL(值作为字面量插入):
// SELECT "id", "name" FROM "users" 
// WHERE tenant_id = tenant-123 AND user_id = user-456

关键特性:

  • ✅ 零反射 - 编译时生成 switch 语句
  • ✅ AOT 兼容 - 完全支持 Native AOT
  • ✅ 类型安全 - 编译时验证变量名和方法签名
  • ✅ 高性能 - O(1) 调度,单次实例转换
  • ✅ 支持静态和实例方法

⚠️ 安全警告: {{var}} 将值作为字面量插入 SQL。仅用于受信任的应用程序控制值(如租户 ID、SQL 关键字)。用户输入必须使用 SQL 参数(@param)。

详见:SqlxVar 完整文档

内联表达式(Inline Expressions)

内联表达式允许在 SQL 中使用表达式、函数和字面量:

// UPDATE 示例:自动递增版本号
[SqlTemplate(@"
    UPDATE {{table}} 
    SET {{set --exclude Id,Version --inline Version=Version+1,UpdatedAt=CURRENT_TIMESTAMP}} 
    WHERE id = @id
")]
Task<int> UpdateAsync(long id, string name, string email);
// 生成: UPDATE [users] SET [name] = @name, [email] = @email, 
//       [version] = [version]+1, [updated_at] = CURRENT_TIMESTAMP WHERE id = @id

// INSERT 示例:设置默认值
[SqlTemplate(@"
    INSERT INTO {{table}} ({{columns --exclude Id}}) 
    VALUES ({{values --exclude Id --inline Status='pending',CreatedAt=CURRENT_TIMESTAMP}})
")]
Task<int> CreateAsync(string name, string description);
// 生成: INSERT INTO [tasks] ([name], [description], [status], [created_at]) 
//       VALUES (@name, @description, 'pending', CURRENT_TIMESTAMP)

支持的表达式:

  • 算术运算:Version=Version+1, Total=@quantity*@unitPrice
  • SQL 函数:CreatedAt=CURRENT_TIMESTAMP, Email=LOWER(TRIM(Email))
  • 字面量:Status='pending', Priority=0, IsActive=1
  • 复杂表达式:Result=COALESCE(NULLIF(Value,''),Default)

关键特性:

  • ✅ 使用属性名(PascalCase),自动转换为列名
  • ✅ 函数内的逗号被正确处理(如 COALESCE(Status,'pending')
  • ✅ 支持嵌套函数和深度括号
  • ✅ 跨数据库方言自动适配
  • ✅ 编译时解析,零运行时开销

各数据库生成的 SQL:

数据库 生成的 SQL
SQLite SELECT [id], [name] FROM [users] WHERE is_active = 1
PostgreSQL SELECT "id", "name" FROM "users" WHERE is_active = true
MySQL SELECT `id`, `name` FROM `users` WHERE is_active = 1

内置仓储接口

继承 ICrudRepository<TEntity, TKey> 获得 46 个标准方法(26 个查询 + 20 个命令):

查询方法(26 个)

  • 单实体查询:GetByIdAsync/GetById, GetFirstWhereAsync/GetFirstWhere
  • 列表查询:GetByIdsAsync/GetByIds, GetAllAsync/GetAll, GetWhereAsync/GetWhere
  • 分页查询:GetPagedAsync/GetPaged, GetPagedWhereAsync/GetPagedWhere
  • 存在性检查:ExistsByIdAsync/ExistsById, ExistsAsync/Exists
  • 计数:CountAsync/Count, CountWhereAsync/CountWhere
  • IQueryable:AsQueryable() - 返回 LINQ 查询构建器

命令方法(20 个)

  • 插入:InsertAndGetIdAsync/InsertAndGetId, InsertAsync/Insert, BatchInsertAsync/BatchInsert
  • 更新:UpdateAsync/Update, UpdateWhereAsync/UpdateWhere, BatchUpdateAsync/BatchUpdate
  • 动态更新DynamicUpdateAsync/DynamicUpdate, DynamicUpdateWhereAsync/DynamicUpdateWhere
  • 删除:DeleteAsync/Delete, DeleteByIdsAsync/DeleteByIds, DeleteWhereAsync/DeleteWhere, DeleteAllAsync/DeleteAll
public interface IUserRepository : ICrudRepository<User, long>
{
    // 继承 46 个标准方法,无需自定义即可使用
    
    // 自定义方法(仅在需要复杂查询时)
    [SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE name LIKE @pattern")]
    Task<List<User>> SearchByNameAsync(string pattern);
}

动态更新(DynamicUpdate)

使用表达式树动态更新指定字段,无需定义自定义方法:

// 更新单个字段
await repo.DynamicUpdateAsync(userId, u => new User { Name = "John" });

// 更新多个字段
await repo.DynamicUpdateAsync(userId, u => new User 
{ 
    Name = "John",
    Age = 30,
    UpdatedAt = DateTime.UtcNow
});

// 使用表达式(递增、计算)
await repo.DynamicUpdateAsync(userId, u => new User 
{ 
    Age = u.Age + 1,
    Score = u.Score * 1.1
});

// 批量更新(带条件)
await repo.DynamicUpdateWhereAsync(
    u => new User { IsActive = false, UpdatedAt = DateTime.UtcNow },
    u => u.LastLoginDate < DateTime.UtcNow.AddDays(-30)
);

优势

  • ✅ 类型安全 - 编译时验证字段名和类型
  • ✅ 灵活 - 支持任意字段组合
  • ✅ 高性能 - 编译时生成代码,零反射
  • ✅ 表达式支持 - 支持算术运算、函数调用

条件占位符

// 动态搜索:只在参数有值时添加条件
[SqlTemplate(@"
    SELECT {{columns}} FROM {{table}} WHERE 1=1 
    {{if notnull=name}}AND name LIKE @name{{/if}}
    {{if notnull=minAge}}AND age >= @minAge{{/if}}
")]
Task<List<User>> SearchAsync(string? name, int? minAge);

对象条件查询

使用 {{where --object}} 从字典自动生成 WHERE 条件(AOT 兼容):

// 定义查询方法
[SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE {{where --object filter}}")]
Task<List<User>> FilterAsync(IReadOnlyDictionary<string, object?> filter);

// 使用:只有非空值会生成条件
var filter = new Dictionary<string, object?>
{
    ["Name"] = "John",      // 生成: [name] = @name
    ["Age"] = 25,           // 生成: [age] = @age
    ["Email"] = null        // 忽略(null 值)
};
var users = await repo.FilterAsync(filter);
// 生成: SELECT ... WHERE ([name] = @name AND [age] = @age)

// 空字典返回 1=1(查询所有)
var all = await repo.FilterAsync(new Dictionary<string, object?>());
// 生成: SELECT ... WHERE 1=1

输出参数(Output Parameters)

Sqlx 支持 SQL 输出参数,用于存储过程、获取生成的 ID 或返回计算值。

同步方法:使用 Out/Ref 参数

Out 参数(Output 模式)
public interface IUserRepository
{
    // 单个输出参数(同步方法)
    [SqlTemplate("INSERT INTO {{table}} (name, age) VALUES (@name, @age); SELECT last_insert_rowid()")]
    int InsertAndGetId(
        string name, 
        int age, 
        [OutputParameter(DbType.Int32)] out int id);
}

// 使用
var result = repo.InsertAndGetId("John", 25, out int newId);
Console.WriteLine($"Inserted ID: {newId}");
Ref 参数(InputOutput 模式)
public interface ICounterRepository
{
    // ref 参数:传入当前值,返回更新后的值(同步方法)
    [SqlTemplate("UPDATE counters SET value = value + 1 WHERE name = @name; SELECT value FROM counters WHERE name = @name")]
    int IncrementCounter(
        string name,
        [OutputParameter(DbType.Int32)] ref int currentValue);
}

// 使用
int counter = 100;
var result = repo.IncrementCounter("page_views", ref counter);
Console.WriteLine($"New counter value: {counter}"); // 输出: 101

异步方法:使用 OutputParameter<T> 包装类

由于 C# 不允许在异步方法中使用 refout 参数,Sqlx 提供了 OutputParameter<T> 包装类:

public interface IUserRepository
{
    // 使用 OutputParameter<T> 包装类(异步方法)
    [SqlTemplate("INSERT INTO users (name, age) VALUES (@name, @age); SELECT last_insert_rowid()")]
    Task<int> InsertUserAsync(
        string name, 
        int age, 
        [OutputParameter(DbType.Int32)] OutputParameter<int> userId);
}

// 使用
var userId = new OutputParameter<int>();
var result = await repo.InsertUserAsync("Alice", 25, userId);
Console.WriteLine($"插入了 {result} 行,ID: {userId.Value}");

// InputOutput 模式(带初始值)
var counter = OutputParameter<int>.WithValue(100);
var result = await repo.IncrementCounterAsync("page_views", counter);
Console.WriteLine($"新值: {counter.Value}"); // 输出: 101

特性

  • 类型安全 - 编译时验证参数类型
  • 双向支持 - out(Output)和 ref(InputOutput)
  • 多参数 - 支持多个输出参数
  • 异步支持 - 通过 OutputParameter<T> 包装类
  • AOT 兼容 - 完全支持 Native AOT

详细示例见 samples/OutputParameterExample.cssamples/StoredProcedureOutputExample.cs

多结果集(Multiple Result Sets)

Sqlx 支持使用元组返回类型从单个 SQL 查询返回多个标量值,在一次数据库调用中高效获取多个相关值。

基本用法

public interface IUserRepository
{
    // 使用 ResultSetMapping 明确指定映射关系
    [SqlTemplate(@"
        INSERT INTO users (name) VALUES (@name);
        SELECT last_insert_rowid();
        SELECT COUNT(*) FROM users
    ")]
    [ResultSetMapping(0, "rowsAffected")]
    [ResultSetMapping(1, "userId")]
    [ResultSetMapping(2, "totalUsers")]
    Task<(int rowsAffected, long userId, int totalUsers)> InsertAndGetStatsAsync(string name);
}

// 使用
var (rows, userId, total) = await repo.InsertAndGetStatsAsync("Alice");
Console.WriteLine($"插入用户 {userId},总用户数: {total}");

默认映射

不指定 [ResultSetMapping] 时使用默认映射:

  • 第1个元素 → 受影响行数(ExecuteNonQuery)
  • 其余元素 → SELECT 结果(按顺序)
[SqlTemplate(@"
    INSERT INTO users (name) VALUES (@name);
    SELECT last_insert_rowid();
    SELECT COUNT(*) FROM users
")]
Task<(int rowsAffected, long userId, int totalUsers)> InsertAndGetStatsAsync(string name);

多个 SELECT 语句

[SqlTemplate(@"
    SELECT COUNT(*) FROM users;
    SELECT MAX(id) FROM users;
    SELECT MIN(id) FROM users
")]
[ResultSetMapping(0, "totalUsers")]
[ResultSetMapping(1, "maxId")]
[ResultSetMapping(2, "minId")]
Task<(int totalUsers, long maxId, long minId)> GetUserStatsAsync();

同步和异步支持

// 异步
[SqlTemplate("...")]
Task<(int rows, long id)> InsertAsync(string name);

// 同步
[SqlTemplate("...")]
(int rows, long id) Insert(string name);

与输出参数结合使用

可以同时使用输出参数和元组返回(需要数据库支持输出参数):

[SqlTemplate(@"
    INSERT INTO users (name, created_at) VALUES (@name, @createdAt);
    SELECT last_insert_rowid();
    SELECT COUNT(*) FROM users
")]
[ResultSetMapping(0, "rowsAffected")]
[ResultSetMapping(1, "userId")]
[ResultSetMapping(2, "totalUsers")]
(int rowsAffected, long userId, int totalUsers) InsertAndGetStats(
    string name,
    [OutputParameter(DbType.DateTime)] ref DateTime createdAt);

注意: SQLite 不支持输出参数,此功能仅适用于 SQL Server、PostgreSQL、MySQL 等数据库。

特性

  • 单次往返 - 一次数据库调用获取所有值
  • 类型安全 - 编译时类型检查
  • 零反射 - 编译时代码生成
  • AOT 兼容 - 完全支持 Native AOT
  • 自动转换 - 自动进行类型转换

详细文档见 docs/multiple-result-sets.md

IQueryable 查询构建器

使用标准 LINQ 语法构建类型安全的 SQL 查询:

// 基本查询
var query = SqlQuery<User>.ForSqlite()
    .Where(u => u.Age >= 18 && u.IsActive)
    .OrderBy(u => u.Name)
    .Take(10);

var sql = query.ToSql();
// SELECT [id], [name], [age], [is_active] FROM [User] 
// WHERE ([age] >= 18 AND [is_active] = 1) 
// ORDER BY [name] ASC LIMIT 10

// 投影查询(匿名类型,完全 AOT 兼容)
var results = await SqlQuery<User>.ForPostgreSQL()
    .Where(u => u.Name.Contains("test"))
    .Select(u => new { u.Id, u.Name })
    .WithConnection(connection)
    .ToListAsync();

// JOIN 查询
var query = SqlQuery<User>.ForSqlite()
    .Join(SqlQuery<Order>.ForSqlite(),
        u => u.Id,
        o => o.UserId,
        (u, o) => new { u.Name, o.Total })
    .Where(x => x.Total > 100);

// 聚合函数
var maxAge = await SqlQuery<User>.ForSqlite()
    .WithConnection(connection)
    .WithReader(UserResultReader.Default)
    .MaxAsync(u => u.Age);

支持的 LINQ 方法:

  • Where, Select, OrderBy, ThenBy, Take, Skip
  • GroupBy, Distinct, Join, GroupJoin
  • Count, Min, Max, Sum, Average
  • First, FirstOrDefault, Any

支持的函数:

  • String: Contains, StartsWith, EndsWith, ToUpper, ToLower, Trim, Substring, Replace, Length
  • Math: Abs, Round, Floor, Ceiling, Sqrt, Pow, Min, Max

表达式查询(仓储模式)

// 在仓储中使用 LINQ 表达式
[SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE {{where --param predicate}}")]
Task<List<User>> GetWhereAsync(Expression<Func<User, bool>> predicate);

// 使用
var adults = await repo.GetWhereAsync(u => u.Age >= 18 && u.IsActive);

表达式占位符(Any Placeholder)

使用 Any.Value<T>() 创建可重用的表达式模板,在运行时填充参数:

// 定义可重用的表达式模板
Expression<Func<User, bool>> ageRangeTemplate = u => 
    u.Age >= Any.Value<int>("minAge") && 
    u.Age <= Any.Value<int>("maxAge");

// 场景 1: 查询年轻用户(18-30岁)
var youngUsers = ExpressionBlockResult.Parse(ageRangeTemplate.Body, SqlDefine.SQLite)
    .WithParameter("minAge", 18)
    .WithParameter("maxAge", 30);
// SQL: ([age] >= @minAge AND [age] <= @maxAge)
// 参数: @minAge=18, @maxAge=30

// 场景 2: 查询中年用户(30-50岁)- 重用同一模板
var middleAgedUsers = ExpressionBlockResult.Parse(ageRangeTemplate.Body, SqlDefine.SQLite)
    .WithParameter("minAge", 30)
    .WithParameter("maxAge", 50);
// SQL: ([age] >= @minAge AND [age] <= @maxAge)
// 参数: @minAge=30, @maxAge=50

// UPDATE 表达式模板
Expression<Func<User, User>> updateTemplate = u => new User
{
    Name = Any.Value<string>("newName"),
    Age = u.Age + Any.Value<int>("ageIncrement")
};

var result = ExpressionBlockResult.ParseUpdate(updateTemplate, SqlDefine.SQLite)
    .WithParameter("newName", "John")
    .WithParameter("ageIncrement", 1);
// SQL: [name] = @newName, [age] = ([age] + @ageIncrement)

使用场景

  • ✅ 查询模板库 - 预定义常用查询模板
  • ✅ 动态查询构建 - 运行时决定参数值
  • ✅ 多租户应用 - 不同租户使用相同模板
  • ✅ 配置驱动查询 - 从配置文件加载参数

API 方法

  • Any.Value<T>(name) - 定义占位符
  • WithParameter(name, value) - 填充单个占位符
  • WithParameters(dictionary) - 批量填充占位符
  • GetPlaceholderNames() - 获取所有占位符名称
  • AreAllPlaceholdersFilled() - 检查是否所有占位符都已填充

批量执行

var users = new List<User> { new() { Name = "Alice" }, new() { Name = "Bob" } };
var sql = "INSERT INTO users (name) VALUES (@name)";
await connection.ExecuteBatchAsync(sql, users, UserParameterBinder.Default);

SqlBuilder - 动态 SQL 构建器

SqlBuilder 提供高性能、类型安全的动态 SQL 构建能力,使用 C# 插值字符串自动参数化,防止 SQL 注入。

核心特性

  • 🔒 自动参数化 - 插值字符串自动转换为 SQL 参数,防止 SQL 注入
  • ⚡ 高性能 - ArrayPool<char> 零堆分配,Expression tree 优化(比反射快 20-34%)
  • 🔧 SqlTemplate 集成 - 支持 {{columns}}、{{table}} 等占位符
  • 🔗 子查询支持 - 组合式查询构建,自动参数冲突解决
  • 📦 AOT 兼容 - 零反射设计,完全支持 Native AOT

快速示例

// 自动参数化
using var builder = new SqlBuilder(SqlDefine.SQLite);
builder.Append($"SELECT * FROM users WHERE age >= {18} AND name = {"John"}");
var template = builder.Build();
// SQL: "SELECT * FROM users WHERE age >= @p0 AND name = @p1"
// Parameters: { "p0": 18, "p1": "John" }

// 动态条件
using var builder2 = new SqlBuilder(SqlDefine.SQLite);
builder2.Append($"SELECT * FROM users WHERE 1=1");

if (nameFilter != null)
    builder2.Append($" AND name LIKE {"%" + nameFilter + "%"}");

if (minAge.HasValue)
    builder2.Append($" AND age >= {minAge.Value}");

var users = await connection.QueryAsync(
    builder2.Build().Sql, 
    builder2.Build().Parameters, 
    UserResultReader.Default
);

SqlTemplate 集成

// 使用 {{columns}}、{{table}} 等占位符
var context = new PlaceholderContext(
    SqlDefine.SQLite, 
    "users", 
    UserEntityProvider.Default.Columns
);

using var builder = new SqlBuilder(context);
builder.AppendTemplate(
    "SELECT {{columns}} FROM {{table}} WHERE age >= @minAge", 
    new { minAge = 18 }
);

var template = builder.Build();
// SQL: "SELECT [id], [name], [age] FROM [users] WHERE age >= @minAge"
// Parameters: { "minAge": 18 }

子查询支持

// 嵌套子查询,自动参数冲突解决
using var subquery = new SqlBuilder(SqlDefine.SQLite);
subquery.Append($"SELECT id FROM orders WHERE total > {1000}");

using var mainQuery = new SqlBuilder(SqlDefine.SQLite);
mainQuery.Append($"SELECT * FROM users WHERE id IN ");
mainQuery.AppendSubquery(subquery);

var template = mainQuery.Build();
// SQL: "SELECT * FROM users WHERE id IN (SELECT id FROM orders WHERE total > @p0)"

性能优化

SqlBuilder 使用 Expression tree 优化匿名对象参数转换:

属性数量 Expression Tree Reflection 性能提升
2 props 1.486 μs 1.632 μs 8.9%
5 props 1.328 μs 1.678 μs 20.9%
10 props 1.507 μs 2.282 μs 34.0%

详细文档见 SqlBuilder 完整指南

SqlxContext - 统一上下文管理

SqlxContext 提供统一的数据库上下文,支持多仓储、事务管理和异常处理:

// 1. 定义上下文
[SqlxContext]
[SqlDefine(SqlDefineTypes.SQLite)]
[IncludeRepository(typeof(UserRepository))]
[IncludeRepository(typeof(OrderRepository))]
public partial class AppDbContext : SqlxContext
{
    // 源生成器自动生成:
    // - 构造函数
    // - Users 和 Orders 属性(懒加载)
    // - 事务传播逻辑
}

// 2. 注册到 DI
services.AddSingleton<UserRepository>();
services.AddSingleton<OrderRepository>();
services.AddSqlxContext<AppDbContext>((sp, options) =>
{
    var connection = sp.GetRequiredService<SqliteConnection>();
    
    // 配置异常处理
    options.OnException = (ex, context) =>
    {
        Console.WriteLine($"SQL Error in {context.MethodName}: {ex.Message}");
    };
    
    // 配置重试
    options.EnableRetry = true;
    options.MaxRetryCount = 3;
    options.RetryDelayMilliseconds = 100;
    
    // 配置日志
    options.Logger = sp.GetRequiredService<ILogger<AppDbContext>>();
    
    return new AppDbContext(connection, options, sp);
}, ServiceLifetime.Singleton);

// 3. 使用事务
app.MapPost("/api/orders", async (CreateOrderRequest request, AppDbContext context) =>
{
    await using var transaction = await context.BeginTransactionAsync();
    try
    {
        var user = await context.Users.GetByIdAsync(request.UserId);
        if (user == null) return Results.NotFound();
        
        var order = new Order { UserId = user.Id, Total = request.Total };
        var orderId = await context.Orders.InsertAndGetIdAsync(order);
        
        await transaction.CommitAsync();
        return Results.Created($"/api/orders/{orderId}", order);
    }
    catch (SqlxException ex)
    {
        // 异常包含完整上下文
        Console.WriteLine($"SQL: {ex.Sql}");
        Console.WriteLine($"Method: {ex.MethodName}");
        Console.WriteLine($"Duration: {ex.DurationMilliseconds}ms");
        Console.WriteLine($"Transaction: {ex.TransactionId}");
        throw;
    }
});

异常处理特性

SqlxException 提供丰富的上下文信息:

try
{
    await repo.GetByIdAsync(123);
}
catch (SqlxException ex)
{
    // 完整的 SQL 上下文
    Console.WriteLine($"SQL: {ex.Sql}");
    Console.WriteLine($"Parameters: {string.Join(", ", ex.Parameters.Select(p => $"{p.Key}={p.Value}"))}");
    Console.WriteLine($"Method: {ex.MethodName}");
    Console.WriteLine($"Repository: {ex.RepositoryType}");
    
    // 性能信息
    Console.WriteLine($"Duration: {ex.DurationMilliseconds}ms");
    
    // 事务信息
    Console.WriteLine($"Transaction ID: {ex.TransactionId}");
    Console.WriteLine($"In Transaction: {ex.InTransaction}");
    
    // 关联 ID(用于分布式追踪)
    Console.WriteLine($"Correlation ID: {ex.CorrelationId}");
    
    // 原始异常
    Console.WriteLine($"Inner: {ex.InnerException?.Message}");
}

自动重试机制

配置自动重试瞬态错误(连接超时、死锁等):

services.AddSqlxContext<AppDbContext>((sp, options) =>
{
    var connection = sp.GetRequiredService<SqliteConnection>();
    
    // 启用重试
    options.EnableRetry = true;
    options.MaxRetryCount = 3;              // 最多重试 3 次
    options.RetryDelayMilliseconds = 100;   // 初始延迟 100ms
    options.UseExponentialBackoff = true;   // 指数退避(100ms, 200ms, 400ms)
    
    return new AppDbContext(connection, options, sp);
});

支持的瞬态错误

  • 连接超时
  • 网络错误
  • 死锁(SQL Server、PostgreSQL、MySQL)
  • 事务冲突
  • 临时资源不可用

全局异常回调

使用 OnException 回调集中处理异常:

options.OnException = (ex, context) =>
{
    // 记录到日志系统
    logger.LogError(ex, 
        "SQL Error in {Repository}.{Method}: {Message}",
        context.RepositoryType,
        context.MethodName,
        ex.Message);
    
    // 发送到监控系统
    telemetry.TrackException(ex, new Dictionary<string, string>
    {
        ["sql"] = context.Sql,
        ["method"] = context.MethodName,
        ["duration"] = context.DurationMilliseconds.ToString()
    });
    
    // 敏感数据已自动清理(密码、令牌等)
};

日志集成

使用 ILogger 自动记录 SQL 执行和错误:

options.Logger = sp.GetRequiredService<ILogger<AppDbContext>>();

// 自动记录:
// - SQL 执行(Debug 级别)
// - 重试尝试(Warning 级别)
// - 错误(Error 级别)

详细文档见 SqlxContext 完整指南

连接和事务管理

连接获取优先级

源生成器按以下优先级查找 DbConnection:

方法参数 > 字段 > 属性 > 主构造函数

// 方式 1: 显式字段(推荐,优先级最高)
[SqlDefine(SqlDefineTypes.SQLite)]
[RepositoryFor(typeof(IUserRepository))]
public partial class UserRepository : IUserRepository
{
    private readonly SqliteConnection _connection;
    public DbTransaction? Transaction { get; set; }
    
    public UserRepository(SqliteConnection connection)
    {
        _connection = connection;
    }
}

// 方式 2: 属性(适合需要外部访问)
public partial class UserRepository : IUserRepository
{
    public SqliteConnection Connection { get; }
    public DbTransaction? Transaction { get; set; }
    
    public UserRepository(SqliteConnection connection)
    {
        Connection = connection;
    }
}

// 方式 3: 主构造函数(最简洁,自动生成)
public partial class UserRepository(SqliteConnection connection) : IUserRepository
{
    // 生成器自动生成:
    // private readonly SqliteConnection _connection = connection;
    // public DbTransaction? Transaction { get; set; }
}

// 方式 4: 方法参数(最灵活,优先级最高)
public interface IUserRepository
{
    // 使用类级别连接
    [SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE id = @id")]
    Task<User?> GetByIdAsync(long id);
    
    // 使用方法参数连接(覆盖类级别连接)
    [SqlTemplate("SELECT {{columns}} FROM {{table}} WHERE id = @id")]
    Task<User?> GetByIdWithConnectionAsync(DbConnection connection, long id);
}

事务支持

var repo = new UserRepository(connection);

using var transaction = connection.BeginTransaction();
repo.Transaction = transaction;

try
{
    await repo.InsertAsync(user1);
    await repo.UpdateAsync(user2);
    await repo.DeleteAsync(user3);
    
    transaction.Commit();
}
catch
{
    transaction.Rollback();
    throw;
}

自动生成规则

  • 如果用户未定义 Transaction 属性,生成器会自动生成
  • 如果用户未定义连接字段/属性,生成器会从主构造函数参数自动生成字段

指标收集(Metrics)

Sqlx 内置支持使用标准 System.Diagnostics.Metrics API 收集 SQL 执行指标(.NET 8+):

// 指标自动记录,无需额外配置
var repo = new UserRepository(connection);
await repo.GetByIdAsync(123);  // 自动记录执行时间、次数等

// 使用 OpenTelemetry 导出指标
using var meterProvider = Sdk.CreateMeterProviderBuilder()
    .AddMeter("Sqlx.SqlTemplate")
    .AddPrometheusExporter()
    .Build();

记录的指标

  • sqlx.template.duration (Histogram) - SQL 执行时间(毫秒)
  • sqlx.template.executions (Counter) - SQL 执行次数
  • sqlx.template.errors (Counter) - SQL 执行错误次数

指标标签(Tags)

  • repository.class - 仓储类全名(如 MyApp.UserRepository
  • repository.method - 方法名(如 GetByIdAsync
  • sql.template - SQL 模板(如 SELECT {{columns}} FROM {{table}} WHERE id = @id
  • error.type - 错误类型(仅错误指标)

特性

  • ✅ 零配置 - 自动记录所有 SQL 执行
  • ✅ 标准 API - 兼容 OpenTelemetry、Prometheus、Application Insights
  • ✅ 高性能 - 编译时生成,零运行时开销
  • ✅ AOT 友好 - 完全支持 Native AOT
  • ✅ 向后兼容 - .NET 8 以下版本为 no-op

详细文档见 指标收集

性能对比

基于 BenchmarkDotNet 测试(.NET 10.0.2 LTS,SQLite 内存数据库):

单行查询性能

ORM Mean vs Sqlx Allocated vs Sqlx
Sqlx 9.447 μs baseline 2.85 KB baseline
Dapper.AOT 10.040 μs +6.3% 2.66 KB -6.7%
FreeSql 55.817 μs +491% 10.17 KB +257%

批量查询性能(不同数据量)

10 行数据
ORM Mean vs Sqlx Allocated vs Sqlx
Dapper.AOT 31.58 μs -5.0% 6.55 KB +6.9%
Sqlx 33.25 μs baseline 6.13 KB baseline
FreeSql 34.63 μs +4.1% 8.67 KB +41.4%
100 行数据
ORM Mean vs Sqlx Allocated vs Sqlx
FreeSql 151.15 μs -14.4% 37.23 KB -3.2%
Dapper.AOT 162.24 μs -8.1% 45.66 KB +18.8%
Sqlx 176.54 μs baseline 38.45 KB baseline
1000 行数据
ORM Mean vs Sqlx Allocated vs Sqlx Gen1 GC
FreeSql 1,290.47 μs -19.2% 318.56 KB -11.9% 9.77
Dapper.AOT 1,433.50 μs -10.2% 432.38 KB +19.5% 23.44
Sqlx 1,596.93 μs baseline 361.69 KB baseline 19.53

关键洞察

  • 单行查询最快 - Sqlx 比 Dapper.AOT 快 6.3%,比 FreeSql 快 5.9倍
  • 内存效率高 - 比 Dapper.AOT 少分配 16.4% 内存(1000行)
  • GC 压力低 - Gen1 GC 比 Dapper.AOT 低 16.7%,比 FreeSql 低 50%
  • 小数据集优势 - 在 Web API 主要场景(10-100行)中性能最优
  • ⚠️ 大数据集权衡 - 1000行时比 FreeSql 慢 19.2%,但内存和 GC 更优

详细数据见 性能基准测试结果

支持的数据库

数据库 方言枚举 状态
SQLite SqlDefineTypes.SQLite ✅ 完全支持
PostgreSQL SqlDefineTypes.PostgreSql ✅ 完全支持
MySQL SqlDefineTypes.MySql ✅ 完全支持
SQL Server SqlDefineTypes.SqlServer ✅ 完全支持
Oracle SqlDefineTypes.Oracle ✅ 完全支持
IBM DB2 SqlDefineTypes.DB2 ✅ 完全支持

推荐: .NET 10 (LTS) - 支持到 2028 年 11 月,性能最佳

高级类型支持

Sqlx 支持多种 C# 类型,自动生成最优代码:

支持的类型

类型 示例 生成策略
Class public class User { } 对象初始化器
Record public record User(long Id, string Name); 构造函数
Mixed Record public record User(long Id, string Name) { public string Email { get; set; } } 构造函数 + 对象初始化器
Struct public struct User { } 对象初始化器
Struct Record public readonly record struct User(long Id, string Name); 构造函数

特性

  • 自动检测类型 - 源生成器自动识别类型并生成最优代码
  • 只读属性过滤 - 自动忽略没有 setter 的属性
  • 混合 Record 支持 - 主构造函数参数 + 额外属性
  • 完全类型安全 - 编译时验证,零运行时开销

示例

// 纯 Record - 使用构造函数
[Sqlx, TableName("users")]
public record User(long Id, string Name, int Age);

// 混合 Record - 构造函数 + 对象初始化器
[Sqlx, TableName("users")]
public record MixedUser(long Id, string Name)
{
    public string Email { get; set; } = "";
    public int Age { get; set; }
}

// 只读属性自动忽略
[Sqlx, TableName("users")]
public class UserWithComputed
{
    [Key] public long Id { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    
    // 只读属性 - 自动忽略
    public string FullName => $"{FirstName} {LastName}";
}

// Struct Record
[Sqlx, TableName("points")]
public readonly record struct Point(int X, int Y);

更多文档

许可证

MIT License - 详见 LICENSE.txt

Product 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 netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.6.9 270 3/3/2026
0.6.8 118 2/7/2026
0.6.7 96 2/5/2026
0.6.6 94 2/3/2026
0.6.5 111 1/23/2026
0.6.4 99 1/22/2026
0.6.3 99 1/22/2026
0.6.2 110 1/16/2026
0.6.1 96 1/15/2026
0.6.0 96 1/15/2026
0.5.0 119 1/11/2026
0.4.0 229 10/27/2025
0.3.0 297 10/2/2025
0.2.0 242 9/21/2025
0.1.4-dev 217 8/31/2025
0.1.3-dev 123 8/23/2025
0.1.2-dev 186 8/20/2025
0.1.1-dev 156 8/17/2025
0.1.0-dev 177 7/27/2025