Inkslab.Linq.PostgreSQL 1.2.81

dotnet add package Inkslab.Linq.PostgreSQL --version 1.2.81
                    
NuGet\Install-Package Inkslab.Linq.PostgreSQL -Version 1.2.81
                    
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="Inkslab.Linq.PostgreSQL" Version="1.2.81" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Inkslab.Linq.PostgreSQL" Version="1.2.81" />
                    
Directory.Packages.props
<PackageReference Include="Inkslab.Linq.PostgreSQL" />
                    
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 Inkslab.Linq.PostgreSQL --version 1.2.81
                    
#r "nuget: Inkslab.Linq.PostgreSQL, 1.2.81"
                    
#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 Inkslab.Linq.PostgreSQL@1.2.81
                    
#: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=Inkslab.Linq.PostgreSQL&version=1.2.81
                    
Install as a Cake Addin
#tool nuget:?package=Inkslab.Linq.PostgreSQL&version=1.2.81
                    
Install as a Cake Tool

Inkslab.Linq

高性能 .NET LINQ-to-SQL ORM 框架,支持 MySQL、SQL Server、PostgreSQL 多数据库引擎,提供类型安全的查询构建、仓储模式、事务管理和批量操作能力。

License: MIT Version .NET GitHub Issues


目录


快速开始

安装 NuGet 包

# 核心库(必选)
dotnet add package Inkslab.Linq

# 数据库适配器(选其一或多个)
dotnet add package Inkslab.Linq.MySql
dotnet add package Inkslab.Linq.SqlServer
dotnet add package Inkslab.Linq.PostgreSQL

# 事务管理(可选)
dotnet add package Inkslab.Transactions

注册服务

using Microsoft.Extensions.DependencyInjection;

// MySQL
services.UseMySql()
    .UseLinq("server=localhost;uid=root;pwd=password;database=mydb;Charset=utf8mb4;");

// SQL Server
services.UseSqlServer()
    .UseLinq("Server=localhost;Database=MyDB;Trusted_Connection=true;");

// PostgreSQL
services.UsePostgreSQL()
    .UseLinq("Host=localhost;Database=mydb;Username=postgres;Password=password;");

注册完成后,通过依赖注入获取以下服务:

服务类型 说明
IQueryable<TEntity> LINQ 查询入口
IRepository<TEntity> 仓储操作(增删改)
IDatabase 原生 SQL 执行
IDatabaseFactory 动态创建数据库实例

项目架构

解决方案结构

inkslab.linq.sln
├── src/
│   ├── Inkslab.Linq/                # 核心抽象层与 LINQ-to-SQL 翻译引擎
│   ├── Inkslab.Linq.MySql/          # MySQL 适配器(MySqlConnector)
│   ├── Inkslab.Linq.SqlServer/      # SQL Server 适配器(Microsoft.Data.SqlClient)
│   ├── Inkslab.Linq.PostgreSQL/     # PostgreSQL 适配器(Npgsql)
│   └── Inkslab.Transactions/        # 事务管理(TransactionUnit)
└── tests/
    ├── Inkslab.Linq.Tests/          # 核心测试(MySQL)
    ├── SqlServer.Tests/             # SQL Server 测试
    ├── PostgreSQL.Tests/            # PostgreSQL 测试
    └── Combination.Tests/           # 多数据库组合测试

核心组件关系

IQueryable<T> ──→ QueryProvider ──→ LINQ Expression Tree ──→ SQL
                                                              │
IRepository<T> ──→ RepositoryExecutor ──→ IDbAdapter ─────────┤
                                              │               │
IDatabase<T> ──→ DatabaseExecutor ────────────┘               │
                       │                                      ▼
                 IDbConnectionPipeline ──→ DbConnection ──→ 数据库

数据库引擎枚举

public enum DatabaseEngine
{
    SQLite = 1, MySQL = 2, SqlServer = 3, PostgreSQL = 4,
    Oracle = 5, DB2 = 6, Sybase = 7
}

实体定义

使用注解将 C# 类映射到数据库表:

using System.ComponentModel.DataAnnotations;
using Inkslab.Linq.Annotations;

[Table("user")]                              // 映射表名
public class User
{
    [Key]                                    // 主键
    [Field("id")]                            // 映射字段名
    [DatabaseGenerated]                      // 数据库自增/生成
    public int Id { get; set; }

    [Field("name")]
    public string Name { get; set; }

    [Version]                                // 乐观锁版本字段
    [Field("date")]
    public DateTime DateAt { get; set; }

    [Field("is_administrator")]
    public bool IsAdministrator { get; set; }

    [Field("nullable")]
    public bool? Nullable { get; set; }
}

注解速查

注解 目标 说明
[Table("table_name")] 指定数据库表名,可选设置 Schema
[Field("column_name")] 属性 指定数据库字段名
[Key] 属性 标记为主键(Update/Delete 以主键为条件)
[DatabaseGenerated] 属性 标记为数据库生成字段(Insert 时忽略)
[Version] 属性 乐观锁版本控制
[Version] 自动值规则
属性类型 自动值策略
int 自增 +1
long DateTime.Now.Ticks
double UTC 时间戳
DateTime DateTime.Now

列值类型映射(数据库 → 实体)

查询结果回填实体时,框架将数据库列值转换为实体属性类型。核心原则:在不丢失数值的前提下,支持「小类型 → 大类型」。

数值类型
  • 字段类型与属性类型一致:直接读取,无额外开销。
  • 无损小转大:自动转换,例如
    • 整数加宽:sbyte/byte/short/ushortintint/uintlonguintulong 等;
    • 浮点/定点加宽:floatdoubleint/long/float/doubledecimaldecimaldouble/float
  • 反向「大转小」(如 longint):执行运行时区间校验,值在范围内则转换,超出范围抛出友好异常(绝不静默截断)
任意类型 → string

实体属性为 string 而数据库字段为其它类型(intlongdecimalboolDateTimeGuid 等)时,框架自动调用其 ToString() 完成转换。

枚举(enum

枚举属性同时支持「数字来源」与「字符串来源」(可空枚举 enum? 同样适用):

  • 数字来源:按枚举底层类型(int/long/…)读取数据库数值,并支持上述无损小转大(如字段 tinyint → 底层 int)。
  • 字符串来源:使用 Enum.Parse(..., ignoreCase: true),同时兼容
    • 枚举名称(如 "Green",忽略大小写);
    • 数字文本(如 "3")。

写入方向(实体 → SQL 参数):枚举按其底层数值类型绑定参数(见 LookupDb)。

分片表定义

表名中使用 [sharding] 占位符,运行时通过 DataSharding(key) 替换:

[Table("user_[sharding]")]         // 如 DataSharding("2024") → "user_2024"
public class UserSharding : User { }

Fluent API 配置(IConfig)

除注解外,还支持 Fluent API 方式配置实体映射:

public interface IConfig<TTable>
{
    IConfigTable Table(string name);
    IConfigCol Field(string name);
}

LINQ 查询

通过注入 IQueryable<T> 进行类型安全的 LINQ 查询,支持完整的 LINQ 方法链。

基础查询

public class UserService
{
    private readonly IQueryable<User> _users;

    public UserService(IQueryable<User> users) => _users = users;

    // Where + OrderBy + 分页
    public async Task<List<User>> GetActiveUsersAsync(int page, int size)
    {
        return await _users
            .Where(u => u.IsAdministrator)
            .OrderByDescending(u => u.DateAt)
            .Skip((page - 1) * size)
            .Take(size)
            .ToListAsync();
    }

    // 投影查询
    public async Task<List<object>> GetUserNamesAsync()
    {
        return await _users
            .Where(u => u.Id > 100)
            .Select(u => new { u.Id, u.Name, DateStr = u.DateAt.ToString() })
            .ToListAsync();
    }
}

Join 查询

// Inner Join
var linq = from u in _users
           join e in _userExes on u.Id equals e.Id
           where u.Id > 100
           select new { u.Name, e.RoleType };

// Left Join(DefaultIfEmpty)
var linq = from u in _users
           join e in _userExes on u.Id equals e.Id into ue
           from e in ue.DefaultIfEmpty()
           select new { u.Name, RoleType = (int?)e.RoleType };

子查询

// 子查询嵌套
var subQuery = from e in _userExes
               where e.RoleType == 1
               select e.Id;

var linq = from u in _users
           where subQuery.Contains(u.Id)
           select u;

分组聚合

var linq = from u in _users
           group new { u.Name, u.DateAt } by new { u.Id, u.Name } into g
           where g.Count() > 1
           orderby g.Key.Id descending
           select new
           {
               g.Key.Id,
               g.Key.Name,
               Total = g.Count(),
               ActiveCount = g.Count(x => x.DateAt > DateTime.Now)
           };

Union / Concat

var query1 = _users.Where(u => u.IsAdministrator).Select(u => new { u.Id, u.Name });
var query2 = _users.Where(u => !u.IsAdministrator).Select(u => new { u.Id, u.Name });

var union = await query1.Union(query2).ToListAsync();       // 去重合并
var concat = await query1.Concat(query2).ToListAsync();     // 直接合并

异步查询方法

所有查询方法均有异步版本,由 QueryableAsync 静态类提供:

方法 说明
ToListAsync() 查询列表
FirstAsync() / FirstOrDefaultAsync() 首条记录
SingleAsync() / SingleOrDefaultAsync() 唯一记录
LastAsync() / LastOrDefaultAsync() 末条记录
CountAsync() / LongCountAsync() 计数
AnyAsync() / AllAsync() 存在性判断
MinAsync() / MaxAsync() 极值
SumAsync() / AverageAsync() 聚合

字符串函数支持

LINQ 中的字符串方法会自动翻译为对应数据库函数:

var result = await _users
    .Where(u => u.Name.Contains("test"))      // LIKE '%test%'
    .Select(u => new
    {
        Sub = u.Name.Substring(0, 5),          // SUBSTRING
        Replaced = u.Name.Replace("a", "b"),   // REPLACE
        Index = u.Name.IndexOf("test"),         // LOCATE / CHARINDEX
        IsEmpty = string.IsNullOrEmpty(u.Name)  // IS NULL OR = ''
    })
    .ToListAsync();

DateTime 成员支持

LINQ 中的 DateTime 属性会自动翻译为各数据库的日期函数:

var result = await _users
    .Where(u => u.DateAt.Year == 2024 && u.DateAt.Month == 12)
    .OrderBy(u => u.DateAt.Date)
    .ToListAsync();

支持的成员:DateYearMonthDayHourMinuteSecondMillisecondDayOfWeekDayOfYearTicksTimeOfDay

注意Ticks 在不同数据库中精度不同,建议使用 Date/Year/Month 等进行日期比较,避免直接比较 Ticks

IQueryable 扩展方法

方法 命名空间 说明
DataSharding(string key) System.Linq 指定分片键
WhereIf(bool test, predicate) System.Linq 条件为真时添加 WHERE
Timeout(int seconds) System.Linq 设置命令超时
NoElementError(string msg) System.Linq 无结果时抛出自定义异常
ToList(int pageIndex, int pageSize) System.Linq 分页查询,返回 PagedList<T>

仓储操作

通过注入 IRepository<TEntity> 进行增删改操作。

插入

// 单条插入
await _userRepository.Into(new User { Name = "张三" }).ExecuteAsync();

// 批量插入
await _userRepository.Into(userList).ExecuteAsync();

// 忽略重复键插入
await _userRepository.Ignore().Into(userList).ExecuteAsync();

// 指定列插入
await _userRepository.Into(user).Limit(x => new { x.Name, x.DateAt }).ExecuteAsync();

// 排除列插入
await _userRepository.Into(user).Except(x => x.DateAt).ExecuteAsync();

// 从查询结果插入
var linq = _users.Where(u => u.Id > 100).Select(u => new User { Name = u.Name });
await _userRepository.InsertAsync(linq);
自增主键反写(PopulateIdentity)

PopulateIdentity() 在插入执行后,将数据库为每一行真实生成的自增 ID 回填到对应实体属性上(不做任何「ID 连续」假设):

var users = new List<User>
{
    new User { Name = "张三" },
    new User { Name = "李四" }
};

await _userRepository.Into(users).PopulateIdentity().ExecuteAsync();

// 执行后 users[0].Id、users[1].Id 已被回填为数据库生成的值

前置条件:实体必须满足「单主键 + [DatabaseGenerated]」,否则在调用处 fail-fast 抛 InvalidOperationException;引擎或 Ignore 组合不支持时抛 NotSupportedException

引擎支持矩阵

场景 支持的引擎 实现方式
反写(非 Ignore) PostgreSQL、SqlServer、MySQL、SQLite(3.35+)、DB2、Sybase RETURNING / OUTPUT INSERTED / LAST_INSERT_ID() / FINAL TABLE / @@IDENTITY
Ignore + 反写 PostgreSQL、MySQL、SQLite 原生 INSERT 忽略并配合 id 返回
不支持 Oracle(批量自增反写)、其余引擎的 Ignore 组合 fail-fast

各路径行为(遵循「能大批量绝不小批量,能小批量绝不单行」):

  • RETURNING 族(PG/SqlServer/SQLite/DB2,非 Ignore):单条多值语句逐行返回,超过 100 行自动拆批,往返 ⌈N/100⌉ 次。
  • 标量族(MySQL/Sybase,含 MySQL Ignore):无多行 RETURNING,改为单条命令内多组「单行 INSERT + 标量回读」(MySQL Ignore 另读 ROW_COUNT() 判定是否被跳过),按参数预算分块,往返 ⌈N/K⌉ 次——绝不逐行往返。
  • RETURNING 族 Ignore(PG/SQLite):逐行执行,按 RETURNING 行数判定是否反写,被冲突跳过的实体保持原值。

更新

// 表达式更新(推荐)
await _userRepository
    .Where(x => x.Id == 1)
    .UpdateAsync(x => new User { Name = "新名称", DateAt = DateTime.Now });

// 实体更新(以主键为条件)
await _userRepository.UpdateTo(user).ExecuteAsync();

// 批量实体更新
await _userRepository.UpdateTo(userList).ExecuteAsync();

// 指定更新列
await _userRepository.UpdateTo(user).Set(x => new { x.Name }).ExecuteAsync();

// 排除更新列
await _userRepository.UpdateTo(user).SetExcept(x => x.DateAt).ExecuteAsync();

// 跳过幂等验证(Version 字段不参与条件)
await _userRepository.UpdateTo(user).SkipIdempotentValid().ExecuteAsync();

删除

// 条件删除
await _userRepository.DeleteAsync(x => x.Id == 1);

// 实体删除(以主键为条件)
await _userRepository.DeleteWith(user).ExecuteAsync();

// 子查询删除
await _userRepository
    .Where(x => _userExes.Where(e => e.RoleType == 2).Select(e => e.Id).Contains(x.Id))
    .DeleteAsync();

仓储方法链

IRepository<TEntity> 支持链式调用,调用顺序为:

IRepository<TEntity>
├── DataSharding(key)              // 可选:指定分片键
│   └── Timeout(seconds)           // 可选:命令超时
│       ├── Where(predicate)       // 条件操作
│       │   ├── Delete()           // 条件删除
│       │   └── UpdateAsync(expr)  // 条件更新
│       ├── Ignore()               // 忽略重复键
│       │   └── Into(entries)      // 插入
│       ├── UpdateTo(entries)      // 实体更新
│       └── DeleteWith(entries)    // 实体删除

动态条件与排序

Conditions 动态条件

Conditions 静态类用于在 LINQ 表达式中构建动态 WHERE 条件(编译时展开,非运行时拼接):

// Conditions.If:条件为真时追加 WHERE
var linq = from u in _users
           where Conditions.If(!string.IsNullOrEmpty(name), u.Name.Contains(name))
              && Conditions.If(roleType.HasValue,
                    _userExes.Where(e => e.RoleType == roleType.Value)
                             .Select(e => e.Id).Contains(u.Id))
           select u;

// Conditions.Conditional:三元条件
var linq = from u in _users
           where Conditions.Conditional(useStrict, u.Id == targetId, u.Id > 0)
           select u;

Conditions API

方法 说明
If(bool test, bool ifTrue) 条件为真时应用 ifTrue
If<T>(T source, bool test, Func<T, bool> ifTrue) 带上下文的条件
Conditional(bool test, bool ifTrue, bool ifFalse) 三元条件
IsTrue<T>(T source, Func<T, bool> predicate) 总是应用条件
True<T>() / False<T>() 返回恒真/恒假表达式
And(expr, expr) 表达式 AND 组合
Or(expr, expr) 表达式 OR 组合
Fragment<T>(expr) 条件片段(用于 IsTrue)

WhereIf 扩展方法

var result = await _users
    .WhereIf(!string.IsNullOrEmpty(name), u => u.Name.Contains(name))
    .WhereIf(isAdmin.HasValue, u => u.IsAdministrator == isAdmin.Value)
    .ToListAsync();

Expression 组合

Expression<Func<User, bool>> predicate = Conditions.True<User>();

if (!string.IsNullOrEmpty(name))
    predicate = predicate.And(u => u.Name.Contains(name));

if (isAdmin.HasValue)
    predicate = predicate.And(u => u.IsAdministrator == isAdmin.Value);

var result = await _users.Where(predicate).ToListAsync();

Ranks 动态排序

Ranks.By 用于在 orderby 子句中实现运行时动态排序:

var linq = from u in _users
           orderby u.DateAt,
                   Ranks.By(u, rank => rank
                       .When(sortType < 100)
                           .OrderBy(x => x.DateAt)
                           .ThenByDescending(x => x.Id)
                       .DefaultByDescending(x => x.DateAt)
                           .DefaultBy(x => x.Id))
           select u;

排序链式接口:IRank<T>.When(bool)IOrderBy<T>.OrderBy()/.OrderByDescending()IThenBy<T>.ThenBy()/.ThenByDescending()/.DefaultBy()/.DefaultByDescending()


原生 SQL 与 IDatabase

通过注入 IDatabase 执行原生 SQL 和存储过程。

查询

// 单条查询
var user = await _database.FirstOrDefaultAsync<User>(
    "SELECT * FROM user WHERE id = @id", new { id = 1 });

// 列表查询
var users = await _database.QueryAsync<User>(
    "SELECT * FROM user WHERE id > @id ORDER BY id LIMIT 10", new { id = 100 });

// 标量查询
var count = await _database.SingleAsync<int>(
    "SELECT COUNT(*) FROM user WHERE is_administrator = @flag", new { flag = true });

执行

var affected = await _database.ExecuteAsync(
    "UPDATE user SET name = @name WHERE id = @id", new { name = "新名称", id = 1 });

多结果集(QueryMultiple)

await using var reader = await _database.QueryMultipleAsync(
    "SELECT * FROM user WHERE id = @id; SELECT COUNT(*) FROM user;", new { id = 1 });

var user = await reader.ReadAsync<User>(RowStyle.FirstOrDefault);
var count = await reader.ReadAsync<int>(RowStyle.Single);

存储过程

var outParam = new DynamicParameter
{
    Direction = ParameterDirection.Output,
    DbType = DbType.String,
    Size = 50
};

var parameters = new Dictionary<string, object>
{
    ["@UserId"] = 1,
    ["@UserName"] = outParam
};

var result = await _database.QueryAsync<User>("GetUserInfo", parameters);
var userName = outParam.Value as string;

ExecuteMultiple 批量命令

await _database.ExecuteMultipleAsync(async executor =>
{
    await executor.ExecuteAsync("UPDATE user SET name = @name WHERE id = @id",
        new { name = "A", id = 1 });
    await executor.ExecuteAsync("UPDATE user SET name = @name WHERE id = @id",
        new { name = "B", id = 2 });
});

DynamicParameter

用于存储过程输出参数和特殊数据类型(如 JSON):

属性 类型 说明
Value object 参数值(输出参数执行后读取)
Direction ParameterDirection Input / Output / InputOutput / ReturnValue
DbType DbType 数据库类型
Size int 参数大小(字符串/二进制输出参数必须指定)
Precision byte 数值精度
Scale byte 小数位数

事务管理

TransactionUnit

TransactionUnit 是基于 AsyncLocal 的事务管理器,支持嵌套和跨服务调用:

using Inkslab.Transactions;

await using (var transaction = new TransactionUnit())
{
    await _userRepository.UpdateAsync(x => new User { DateAt = DateTime.Now });
    await _userRepository.Into(newUser).ExecuteAsync();

    await transaction.CompleteAsync();  // 显式提交;不调用则自动回滚
}

构造参数

TransactionOption 说明
Required 加入现有事务,无则创建新事务(默认)
RequiresNew 始终创建新事务(挂起外层事务)
Suppress 不参与事务
new TransactionUnit()                                          // 默认
new TransactionUnit(TransactionOption.RequiresNew)             // 强制新事务
new TransactionUnit(TransactionOption.Required, IsolationLevel.ReadCommitted)

嵌套事务

await using (var outer = new TransactionUnit())
{
    await _userRepository.UpdateAsync(x => new User { DateAt = DateTime.Now });

    await using (var inner = new TransactionUnit())  // 加入外层事务
    {
        await _userRepository.Into(newUser).ExecuteAsync();
        await inner.CompleteAsync();
    }

    await outer.CompleteAsync();  // 整体提交
}

连接复用 SerializableScope

SerializableScope 在作用域内对相同连接字符串复用同一个数据库连接,适用于批量操作场景:

using Inkslab.Linq;

await using (var scope = new SerializableScope())
{
    var user = await _users.FirstOrDefaultAsync();
    var count = await _users.CountAsync();

    for (int i = 0; i < 100; i++)
    {
        await _userRepository
            .Where(x => x.Id == i)
            .UpdateAsync(x => new User { DateAt = DateTime.Now });
    }
}  // 连接自动释放

与事务配合

await using (var scope = new SerializableScope())
{
    await using (var transaction = new TransactionUnit())
    {
        await _userRepository.Into(users).ExecuteAsync();
        await transaction.CompleteAsync();
    }
}
  • 支持嵌套,内层 Scope 复用外层连接
  • 基于 AsyncLocal 实现,线程安全
  • 在事务环境中,连接由事务管理

批量操作

通过仓储批量插入

int rows = await _userRepository.Timeout(100).Ignore().Into(users).ExecuteAsync();

通过 DataTable 批量写入

var dt = new DataTable("user");
dt.Columns.Add("name", typeof(string));
dt.Columns.Add("date", typeof(DateTime));

for (int i = 0; i < 1000; i++)
    dt.Rows.Add($"User_{i}", DateTime.Now);

int rows = await _database.WriteToServerAsync(dt);

批量更新 / 删除

int rows = await _userRepository.UpdateTo(userList).ExecuteAsync();
int rows = await _userRepository.DeleteWith(userList).ExecuteAsync();

JSON/JSONB 支持

框架提供 JsonPayloadJsonbPayload 类型用于 PostgreSQL 的 JSON/JSONB 字段:

[Table("user_contents")]
public class UserContents
{
    [Key][Field("id")][DatabaseGenerated]
    public int Id { get; set; }

    [Field("content")]
    public JsonbPayload Content { get; set; }
}

// 插入
await _repository.Into(new UserContents
{
    Content = new JsonbPayload("{\"name\":\"test\"}")
}).ExecuteAsync();

// 通过 DynamicParameter 指定 JSONB 类型
var param = new DynamicParameter
{
    Value = "{\"name\":\"test\"}",
    DbType = LookupDb.JsonbDbType,
    Direction = ParameterDirection.Input
};
类型 DbType 用途
JsonPayload LookupDb.JsonDbType PostgreSQL JSON 字段
JsonbPayload LookupDb.JsonbDbType PostgreSQL JSONB 字段

支持在 LINQ 查询和批量操作(WriteToServerAsync)中使用。


分片表

[Table("user_[sharding]")]
public class UserSharding : User { }

// 查询
var users = await _userShardings
    .DataSharding("2024")
    .Where(x => x.Id > 100)
    .ToListAsync();

// 仓储操作
await _userShardingRepository
    .DataSharding("2024")
    .Into(userList)
    .ExecuteAsync();

多数据库配置

定义连接字符串类

public class PromotionConnectionStrings : IConnectionStrings
{
    public string Strings => "Server=localhost;Database=Promotion;Trusted_Connection=true;";
}

注册多数据库

services.UseMySql()
    .UseLinq("server=localhost;uid=root;pwd=password;database=main;");

services.UseSqlServer()
    .UseDatabase<PromotionConnectionStrings>();

使用

public class MyService
{
    private readonly IQueryable<User> _users;
    private readonly IDatabase<PromotionConnectionStrings> _promDb;

    public MyService(IQueryable<User> users, IDatabase<PromotionConnectionStrings> promDb)
    {
        _users = users;
        _promDb = promDb;
    }
}

IDatabaseFactory 动态创建

services.AddDatabaseFactory();

var db = _databaseFactory.Create(DatabaseEngine.MySQL, connectionString);
var users = await db.QueryAsync<User>("SELECT * FROM user LIMIT 10");

SQL 分析器 LinqAnalyzer

无需数据库连接即可将 LINQ 表达式翻译为 SQL,适用于单元测试和 SQL 预览:

using Inkslab.Linq;

var users = LinqAnalyzer.From<User>(DatabaseEngine.MySQL, mySqlAdapter);

// 翻译为 CommandSql
CommandSql<List<User>> sql = users.Where(u => u.Id > 100).ToSql();

// 翻译为 SQL 字符串
string sqlText = users.Where(u => u.Id > 100).ToSqlString();

// 复杂查询翻译
string sqlText = users.ToSqlString(q => q.Where(u => u.Id > 100).Count());

API 速查表

IRepository<TEntity> 接口继承链

IRepository<TEntity>
  ├── DataSharding(string shardingKey) → IRepositoryDataSharding<TEntity>
  │     └── Timeout(int commandTimeout) → IRepositoryTimeout<TEntity>
  │           ├── Where(predicate) → IRepositoryCondition<TEntity>
  │           │     ├── TakeWhile(predicate)
  │           │     ├── SkipWhile(predicate)
  │           │     ├── Delete() / DeleteAsync()
  │           │     └── (链式 Where)
  │           ├── Ignore() → IRepositoryIgnore<TEntity>
  │           │     ├── Into(entries) → IInsertable<TEntity>
  │           │     │     ├── Limit(columns) / Except(columns) → ICommandExecutor
  │           │     │     ├── PopulateIdentity() → IInsertable<TEntity>  // 自增主键反写
  │           │     │     ├── Execute() / ExecuteAsync()
  │           │     │     └── (直接执行)
  │           │     ├── Insert(IQueryable) / InsertAsync(IQueryable)
  │           │     └── (直接插入)
  │           ├── UpdateTo(entries) → IUpdateable<TEntity>
  │           │     ├── Set(columns) / SetExcept(columns) → IUpdateableOfSet<TEntity>
  │           │     │     └── SkipIdempotentValid() → ICommandExecutor
  │           │     └── Execute() / ExecuteAsync()
  │           └── DeleteWith(entries) → IDeleteable<TEntity>
  │                 ├── SkipIdempotentValid() → ICommandExecutor
  │                 └── Execute() / ExecuteAsync()

IDatabase 方法一览

方法 说明
Single<T> / SingleAsync<T> 查询唯一值(无结果抛异常)
SingleOrDefault<T> / SingleOrDefaultAsync<T> 查询唯一值或默认值
First<T> / FirstAsync<T> 查询首条(无结果抛异常)
FirstOrDefault<T> / FirstOrDefaultAsync<T> 查询首条或默认值
Query<T> / QueryAsync<T> 查询列表
QueryMultiple / QueryMultipleAsync 多结果集查询
Execute / ExecuteAsync 执行非查询命令
WriteToServer / WriteToServerAsync 批量写入
ExecuteMultiple / ExecuteMultipleAsync 批量命令执行

CommandSql 对象

public class CommandSql
{
    public string Text { get; }                                    // SQL 文本
    public CommandType CommandType { get; }                        // Text / StoredProcedure
    public IReadOnlyDictionary<string, object> Parameters { get; } // 参数字典
    public int? Timeout { get; set; }                              // 超时(秒)
}
// StoredProcedureCommandSql : CommandSql — 存储过程
// CommandSql<T> : CommandSql — 带 RowStyle、DefaultValue、NoElementError

RowStyle 枚举

说明
First 首条(无结果抛异常)
FirstOrDefault 首条或默认值
Single 唯一(多条或无结果抛异常)
SingleOrDefault 唯一或默认值

异常体系

异常类 基类 说明
DSyntaxErrorException SyntaxErrorException SQL 语法错误
NoElementException CodeException 查询无结果(errorCode=1)

测试

项目使用 xunitPlus 测试框架,支持构造函数依赖注入。

测试 Startup

public class Startup : XunitPlus.Startup
{
    public Startup(Type serviceType) : base(serviceType) { }

    public override void ConfigureServices(IServiceCollection services, HostBuilderContext context)
    {
        services.UseMySql()
            .UseLinq("server=localhost;uid=root;pwd=password;database=test_db;Charset=utf8mb4;");
        services.AddDatabaseFactory();
        services.AddLogging(builder => builder.AddDebug().SetMinimumLevel(LogLevel.Debug));
        base.ConfigureServices(services, context);
    }
}

测试示例

public class UserTests
{
    private readonly IRepository<User> _userRepository;
    private readonly IQueryable<User> _users;

    public UserTests(IRepository<User> userRepository, IQueryable<User> users)
    {
        _userRepository = userRepository;
        _users = users;
    }

    [Fact]
    public async Task QueryUsers()
    {
        var users = await _users.Where(u => u.Id > 0).Take(10).ToListAsync();
        Assert.NotNull(users);
    }
}

运行测试

dotnet test
dotnet test tests/Inkslab.Linq.Tests/

开发与构建

环境要求

  • .NET 6.0 SDK 或更高版本
  • C# 9.0(LangVersion=9.0strict 模式)

构建

dotnet build

打包

.\build.ps1
# 输出:.nupkgs/
# Inkslab.Transactions, Inkslab.Linq, Inkslab.Linq.MySql,
# Inkslab.Linq.SqlServer, Inkslab.Linq.PostgreSQL

包依赖

版本
Inkslab [1.2.23, 2.0.0)
Microsoft.Extensions.Logging.Abstractions 3.1.0
Microsoft.Extensions.DependencyInjection.Abstractions 3.1.0
Microsoft.Extensions.ObjectPool 6.0.36

许可证

MIT License - Copyright (c) 2023 Yuanli He

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  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 was computed.  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 was computed.  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 was computed.  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
1.2.81 89 6/10/2026
1.2.80 89 6/9/2026
1.2.78 93 5/29/2026
1.2.77 99 4/27/2026
1.2.76 107 4/22/2026
1.2.75 103 4/21/2026
1.2.74 102 4/9/2026
1.2.73 105 4/9/2026
1.2.72 107 4/8/2026
1.2.70 104 4/7/2026
1.2.69 105 4/3/2026
1.2.68 103 3/31/2026
1.2.67 111 3/27/2026
1.2.66 106 3/24/2026
1.2.65 124 3/16/2026
1.2.64 122 3/13/2026
1.2.61 106 2/24/2026
1.2.60 108 2/14/2026
1.2.59 109 2/14/2026
1.2.58 111 2/6/2026
Loading failed