Skip to content

j4587698/TinyDb

Repository files navigation

TinyDb

轻量级 AOT 兼容的嵌入式 NoSQL 数据库

License: MIT NuGet NuGet Downloads codecov .NET Version AOT Compatible CI

中文 | English


AOT 优先声明

TinyDb 是一个 AOT 优先、受 LiteDB 启发的单文件嵌入式 NoSQL 数据库:

  • 以 AOT 为准:一切功能以 NativeAOT 编译后的行为为准,不提供任何回退逻辑。
  • 开发一致性:保障开发期(非 AOT/JIT)与 AOT 发布后的行为一致。
  • 非 AOT 场景建议:如果你的应用不需要 NativeAOT,推荐直接使用 LiteDB(生态更成熟、特性更完整)。

特性亮点

  • 单文件数据库 - 所有数据存储在一个文件中,部署简单
  • 100% AOT 兼容 - 完全支持 Native AOT 编译,无反射依赖
  • 源代码生成器 - 编译时生成序列化代码,零运行时开销
  • LINQ 查询 - 完整的 LINQ 支持,类型安全的查询体验
  • 动态字符串与 SQL 子集 - 支持 AOT 兼容的字符串条件、Executeselect/insert/update/delete
  • ACID 事务 - 完整的事务支持,保证数据一致性
  • 密码保护 - 内置数据库级别加密保护
  • 只读模式 - 支持以只读方式打开数据库,安全共享数据文件
  • 高性能索引 - B+树索引,支持快速数据检索
  • 跨平台 - 支持 Windows、Linux、macOS

快速开始

安装

dotnet add package TinyDb

定义实体

using TinyDb.Attributes;
using TinyDb.Bson;

[Entity("users")]
public partial class User
{
    [Id]
    public ObjectId Id { get; set; } = ObjectId.NewObjectId();

    [Index]
    public string Name { get; set; } = "";

    [Index(Unique = true)]
    public string Email { get; set; } = "";

    public int Age { get; set; }

    [BsonIgnore]  // 此属性不会被序列化
    public string? TempToken { get; set; }

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

基本 CRUD 操作

using TinyDb.Core;

// 创建/打开数据库
using var db = new TinyDbEngine("myapp.db");
var users = db.GetCollection<User>();

// 插入
var user = new User { Name = "张三", Email = "zhangsan@example.com", Age = 25 };
users.Insert(user);

// 查询
var found = users.Find(u => u.Age > 20).ToList();
var one = users.FindOne(u => u.Email == "zhangsan@example.com");

// 更新
user.Age = 26;
users.Update(user);

// 删除
users.Delete(user.Id);

LINQ 查询

// 复杂查询
var results = users.Query()
    .Where(u => u.Age >= 18 && u.Age <= 30)
    .Where(u => u.Name.StartsWith("张"))
    .OrderByDescending(u => u.CreatedAt)
    .Skip(10)
    .Take(20)
    .ToList();

// 聚合查询
var count = users.Query().Where(u => u.Age > 20).Count();
var exists = users.Query().Any(u => u.Email.Contains("@gmail.com"));

字符串查询与 SQL Execute

TinyDb 提供一组 AOT 兼容的动态查询入口。所有动态条件和 SQL 都会先解析成内部 AST,不使用运行时动态代码生成。

字符串条件查询

using TinyDb.Query;

var adults = users.Find(
    "Age >= @minAge and Name startswith @prefix",
    QueryParams.Create(("minAge", 18), ("prefix", "张")))
    .ToList();

var page = users.Find(
    "Age >= @minAge",
    QueryParams.Create(("minAge", 18)),
    skip: 20,
    limit: 10)
    .ToList();

字符串条件支持:

  • 字段路径:IdNameAddress.City,其中 Id / id / _id 会按主键处理。
  • 比较:===!=<>>>=<<=,以及 eqnegtgteltlte
  • 逻辑:andornot、括号。
  • 空值:is nullis not null
  • 字符串匹配:containsstartswith / starts_withendswith / ends_with
  • like:支持 abc%%abc%abc% 和精确匹配,不支持 _ 或中间多段 % 通配。
  • 函数:contains(field, value)startswith(field, value)endswith(field, value)lower(field)upper(field)trim(field)
  • 参数:@name,推荐用 QueryParams.Create(("name", value)) 传入。

暂不支持:

  • in / not in、子查询、正则表达式。
  • 任意 .NET 方法调用;只支持上面列出的固定函数。
  • 匿名对象投影;AOT 下请使用 BsonDocument 或带 [Entity] 的 DTO。

SQL 查询与 DML

推荐新代码统一使用 Execute

// SELECT -> BsonDocument,字段名按 SELECT 里写的名字返回
var select = users.Execute(
    "select Id, Name from users where Age >= @age order by Age desc limit 10 offset 0",
    QueryParams.Create(("age", 18)));

foreach (var doc in select.Documents)
{
    var id = doc["Id"];
    var name = doc["Name"].ToString();
}

// SELECT * 会展开为实体属性名
var all = users.Execute("select * from users where Id = @id", QueryParams.Create(("id", id)));
var userName = all.Documents.Single()["Name"].ToString();

// 泛型 DTO 投影,DTO 需要 [Entity] 并参与源生成
var summaries = users.Execute<UserSummary>(
    "select Id, Name from users where Age >= @age",
    QueryParams.Create(("age", 18))).Rows;

// INSERT / UPDATE / DELETE 返回影响行数
var inserted = users.Execute(
    "insert into users (Id, Name, Email, Age) values (@id, @name, @email, @age)",
    QueryParams.Create(("id", ObjectId.NewObjectId()), ("name", "李四"), ("email", "lisi@example.com"), ("age", 22)));

var updated = users.Execute(
    "update users set Name = @name, Age = @age where Id = @id",
    QueryParams.Create(("id", id), ("name", "王五"), ("age", 23)));

var deleted = users.Execute(
    "delete from users where Id = @id",
    QueryParams.Create(("id", id)));

SQL 支持:

  • select * from collection
  • select Id, Name from collection
  • select Name as DisplayName from collection
  • where:复用字符串条件查询语法。
  • order by Field asc|desc,支持多字段排序。
  • limitoffset,支持字面量或参数。
  • insert into collection (Field1, Field2) values (@v1, @v2)
  • update collection set Field = @value where ...
  • delete from collection where ...
  • 字面量:字符串、数字、truefalsenull
  • DML 写入会按目标实体属性类型归一化数值字面量,避免 AOT 反序列化时因 BSON 类型不匹配而丢值。
  • FindSqlFindSqlDocumentsFindSql<TProjection> 仍保留兼容,但内部已走 Execute
  • TinyDbEngine.Execute<TSource>(...)TinyDbEngine.Execute<TSource, TProjection>(...) 可从引擎层按 SQL 中的集合名路由。

注意:update / deletewhere 按 SQL 语义可省略;省略时会影响整个集合。批量写入建议显式添加 where,需要事务时用 TinyDb 事务 API 包裹。

SQL 暂不支持:

  • join;请使用 Include(...) 做实体引用加载。
  • group byhaving、聚合函数、窗口函数。
  • 子查询、CTE、union
  • insert into ... select ...
  • update set Age = Age + 1 这类表达式赋值;当前只支持字面量或参数赋值。
  • 更新主键 Id / _id
  • DML 的嵌套字段写入;当前 insert / update 只支持顶层字段。
  • DML 重复写入同一字段;insert 字段列表和 update set 中重复字段会被拒绝。
  • SQL 事务语法;需要事务时使用 BeginTransaction() / Commit() / Rollback() 包裹。

Include 与 SQL 的关系

Include(...) 不是 SQL join,它是在实体查询后按 DBRef / 外键元数据加载引用:

var orders = db.GetCollection<Order>()
    .Include("Customer")
    .FindSql("select * from orders where Total >= @min", QueryParams.Create(("min", 100)))
    .ToList();

Include 面向实体结果,不适用于 FindSqlDocuments / Execute(...).Documents 这种动态文档投影。

密码保护与数据页加密

Password 可用于兼容的数据库密码保护。创建新库时同时设置 EnableEncryption = true,会启用数据页和 WAL payload 加密。已有明文库设置 EnableEncryption = true 不会被隐式迁移,会抛出异常;请显式导出或 compact 到新的加密库。

// 创建加密数据库
var options = new TinyDbOptions
{
    EnableEncryption = true,
    Password = "MySecurePassword123!"
};
using var secureDb = new TinyDbEngine("secure.db", options);

// 访问加密数据库
using var db = new TinyDbEngine("secure.db", new TinyDbOptions { Password = "MySecurePassword123!" });

只读模式

TinyDb 支持以只读模式打开数据库。只读模式下多个进程可安全共享同一个数据文件,无需担心写入锁冲突。

// 以只读模式打开数据库
var options = new TinyDbOptions
{
    ReadOnly = true
};
using var db = new TinyDbEngine("myapp.db", options);

// 只读模式下仅支持查询操作
var users = db.GetCollection<User>();
var user = users.FindOne(u => u.Id == id);

// 写入操作会抛出 InvalidOperationException
// users.Insert(newUser);  // ❌

事务支持

using var db = new TinyDbEngine("myapp.db");
var users = db.GetCollection<User>();
var orders = db.GetCollection<Order>();

// 开启事务
db.BeginTransaction();
try
{
    users.Insert(new User { Name = "新用户" });
    orders.Insert(new Order { UserId = "...", Amount = 99.99m });
    db.Commit();  // 提交事务
}
catch
{
    db.Rollback();  // 回滚事务
    throw;
}

高级特性

属性标注

属性 说明
[Entity("集合名")] 标记实体类,指定集合名称
[Id] 标记主键属性
[Index] 创建索引
[Index(Unique = true)] 创建唯一索引
[BsonIgnore] 序列化时忽略此属性
[BsonField("字段名")] 自定义 BSON 字段名

支持的数据类型

  • 基本类型: int, long, double, decimal, bool, string, DateTime, Guid
  • 可空类型: int?, DateTime?
  • 集合类型: List<T>, T[], Dictionary<string, T>
  • 嵌套对象: 支持复杂对象嵌套
  • 特殊类型: ObjectId, BsonDocument

配置选项

var options = new TinyDbOptions
{
    Password = "密码",           // 数据库密码(可选)
    EnableEncryption = true,    // 创建新库时启用数据页和 WAL 加密
    ReadOnly = false,           // 以只读方式打开(默认 false,写入模式)
    PageSize = 8192,            // 页面大小(默认 8KB)
    CacheSize = 1000,           // 缓存页数
    EnableJournaling = true,    // 启用 WAL 日志
    Timeout = TimeSpan.FromMinutes(5), // 操作超时时间
    Logger = (level, message, ex) =>
    {
        Console.WriteLine($"[{level}] {message}");
        if (ex != null) Console.WriteLine(ex);
    }
};

Logger 是可选回调,签名为 Action<TinyDbLogLevel, string, Exception?>,支持级别:DebugInformationWarningErrorCritical

性能数据

以下为 BenchmarkDotNet 最新实测均值(QuickIndexBenchmark,2026-07-10):

操作 SynchronousWrites=true SynchronousWrites=false 内存分配(true / false)
Insert1000_Individual 8,976,726.1 μs(约 111 ops/s 412,939.3 μs(约 2,422 ops/s 12.60 MB / 10.80 MB
Insert1000_Batch 297,528.8 μs(约 3,361 ops/s 215,342.3 μs(约 4,644 ops/s 13.27 MB / 13.49 MB
QueryWithoutIndex 752.2 μs 1,827.3 μs 280.56 KB / 292.28 KB
QueryWithIndex 525.3 μs 737.6 μs 74.23 KB / 76.57 KB
QueryWithUniqueIndex 331.2 μs 419.0 μs 18.16 KB / 18.24 KB
FindById 267.3 μs 295.0 μs 6.59 KB / 6.67 KB

注: 测试环境为 AMD EPYC 7763 2.44GHz,.NET 9.0.12。该组基准使用 EnableJournaling=false,用于对比核心读写路径。

版本历史

v0.6.0 (当前)

  • 只读模式:新增只读打开数据库支持,多进程可安全共享数据文件,只读时所有写入操作抛出 InvalidOperationException
  • 写入锁文件:通过锁文件机制强制单写者访问,防止多进程同时写入导致数据损坏。
  • 推迟空闲页扫描:只读模式下延迟空闲页回收,优化打开速度和并发安全。
  • 存储层安全性强化:修复 DiskStream 释放线程安全、大文档链完整性校验、WAL 路径穿越防护。
  • 查询表达式增强:新增 string.IsNullOrEmptyIsNullOrWhiteSpace 查询函数支持。
  • 配置选项验证TinyDbOptions 构造时进行有效性校验,早期发现非法配置。
  • CI 工作流完善:为 TinyDb.csproj 变更自动触发 CI,版本号变化时执行完整发布流程。
  • 回归验证补齐:新增只读模式、单写者锁、存储安全相关的回归测试。

v0.5.0

  • 并发与写入路径强化:细化集合写锁、页锁和文档锁边界,降低并发写入、事务提交和缓存回写路径的锁竞争。
  • WAL 与持久化安全增强:加强同步刷盘、批量提交、回放校验和事务恢复路径,覆盖更多崩溃恢复与半写盘场景。
  • 查询与 SQL 执行完善:补强动态 SQL/DML、运行时表达式绑定、索引规划、排序/TopK 和事务可见性相关逻辑。
  • AOT 与源码生成器稳定性提升:拆分并强化源码生成器类型分析、依赖分析、字段命名和 mapper 生成路径,减少 AOT/trim 边界问题。
  • 序列化与 BSON 兼容增强:强化 Decimal128ObjectIdDateTime、数值转换、复杂集合和嵌套对象的往返转换。
  • 性能基线刷新:更新 QuickIndexBenchmark 基准数据,当前报告覆盖写入、索引查询、唯一索引查询和主键查找的耗时与分配。
  • 回归验证补齐:新增并扩展并发、WAL、索引、查询、AOT、加密、源码生成器和序列化回归测试。

v0.4.5

  • 动态查询与 SQL 子集:新增 AOT 兼容的字符串条件查询、统一 Execute SQL 入口、BsonDocument 动态投影、泛型 DTO 投影,以及基础 select/insert/update/delete 解析执行。
  • SQL 能力边界文档化:明确列出当前支持的条件语法、投影规则、DML 范围,以及暂不支持的 join、聚合、子查询、表达式赋值等能力。
  • WAL 崩溃恢复增强:页写入前先追加并刷新 WAL,重放时会校验磁盘页头、页号和页校验和,即使半写盘页带有最新 LSN 也会从 WAL 恢复。
  • 页校验升级:页 checksum 从旧累加和升级为 CRC32,提升半写盘和静默损坏检测能力。
  • 旧库兼容:校验逻辑同时接受 CRC32 和旧累加和 checksum,旧库无需迁移,页面重新写入后自然升级为 CRC32。
  • 回归验证补齐:新增 WAL 半写盘恢复、CRC32 零区间计算和旧 checksum 兼容测试。

v0.4.4

  • 并发初始化修复:串行化集合注册、集合状态构建和索引管理器创建,避免 ConcurrentDictionary.GetOrAdd 工厂在并发下重复执行带副作用的 schema、元数据和索引页初始化逻辑。
  • Schema 写入竞态修复MetadataManager.EnsureSchema() 增加同步与二次检查,避免首次并发访问同一实体时重复写入 __sys_catalog
  • ASP.NET 元数据兼容增强BsonConversion 原生支持 JsonElement / JsonDocument,可递归转换 Dictionary<string, object> 中来自 JSON 请求体的对象、数组、数值、布尔值和 null
  • 回归验证补齐:新增并发首次访问带索引集合的重开库验证,以及 JsonElement 递归 BSON 转换测试。

v0.4.3

  • 新库初始化修复:新建数据库时立即写入并刷新有效 Header,避免应用重启后出现 Invalid database header
  • WAL 安全修复:删除主数据库文件但遗留 WAL 时,创建新库会忽略旧 WAL,避免旧日志污染新数据库。
  • 索引可靠性增强EnsureIndex() 支持回填已有文档,持久化索引根页并在重启后恢复,回填失败不会留下无效索引定义。
  • 事务可见性修复FindById() 可见当前事务中的挂起插入/删除。
  • AOT 验证增强:清理 AOT/trim 警告并补充 NativeAOT 回归测试。

v0.4.2

  • AOT 复杂集合修复:恢复并增强 List<复杂类型> 在 AOT 模式下的反序列化支持,覆盖 Entity 依赖类型中的复杂对象集合场景。
  • 源生成器增强:补齐依赖复杂类型路径中的集合/字典元数据与专用序列化分支,避免 List element type ... is not supported in AOT mode 异常。
  • 回归验证补齐:新增相关往返序列化测试,并通过 AOT 发布后二进制运行验证。

v0.4.1

  • 分页计数增强:新增 Find(..., skip, limit, out totalCount),支持一次查询同时返回分页结果与总数。
  • Query 体验增强:新增 Query().Count(out totalCount) 扩展,支持在 Skip/Take 链路中同步获取总数与分页结果。

v0.4.0

  • 依赖精简:移除 Microsoft.IO.RecyclableMemoryStreamSystem.IO.HashingSystem.IO.Pipelines 外部依赖,改为内置最小兼容实现。
  • 性能优化:减少 ToArray 等中间分配,优化批量写入与无索引全表扫描路径。

v0.3.2

  • 查询 API 增强:新增 Find 重载,支持更灵活的查询调用方式。

v0.3.1

  • 并发一致性修复:新增集合级写入串行化,避免高并发写入下的索引冲突与提交竞态。
  • 错误传播强化:对关键数据安全路径(如 Flush、页切换、新页回退失败等)不再吞异常,直接抛出给上层处理。
  • 扫描性能优化:Raw 扫描从“整集合完整快照”改为按页紧凑快照(Snapshot(false)),降低扫描额外开销。
  • 可观测性增强:新增 TinyDbOptions.Logger 回调配置,支持 TinyDbLogLevel 分级日志。

v0.3.0

  • 性能飞跃:通过 Span 与池化缓冲区(Pooled Buffers)实现零/低分配序列化,内存分配降低 90%+
  • 核心重构:引入高性能响应式组提交(Reactive Group Commit)与锁剥离(Lock Stripping)技术
  • 元数据重构:深度重构元数据管理系统,提升架构清晰度与扩展性
  • 查询优化:支持谓词下推(Predicate Push-down)与排序/分页下推,提升 BSON 扫描效率
  • 异步支持:新增真正的异步读取 API 接口

v0.2.0

  • 完善 [BsonIgnore] 属性支持
  • 新增 AOT 兼容的序列化测试
  • 修复源生成器相关问题
  • 2610 个测试全部通过

v0.1.5

  • 完善 DbRef 引用支持
  • 增强嵌套类 Entity 支持
  • 性能优化

项目结构

TinyDb/
├── TinyDb/                    # 核心库
├── TinyDb.SourceGenerator/    # 源代码生成器
├── TinyDb.Tests/              # 测试项目
├── TinyDb.Demo/               # 演示项目
└── TinyDb.UI/                 # 可视化管理工具

运行演示

dotnet run --project TinyDb.Demo

开发环境

  • .NET 8.0 / 9.0 / 10.0
  • C# 12+
  • 推荐 IDE: Rider / Visual Studio 2022

贡献

欢迎提交 Issue 和 Pull Request!

# 运行测试
dotnet test

# AOT 编译测试
dotnet publish -c Release -r win-x64 --self-contained -p:PublishAot=true

许可证

MIT License - 可自由用于商业项目


如果这个项目对你有帮助,请给一个 ⭐ Star!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages