在 TinyDb 里,ITinyCollection<T> 同时提供同步与异步、单条与批量 API。本文聚焦同步路径,下一篇讲异步。

1. 单条 CRUD 基础范式

using TinyDb.Attributes;
using TinyDb.Bson;
using TinyDb.Core;

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

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

    public decimal Price { get; set; }
    public int Stock { get; set; }
}

using var db = new TinyDbEngine("crud.db");
var products = db.GetCollection<Product>();

// C
var p = new Product { Name = "机械键盘", Price = 299m, Stock = 100 };
products.Insert(p);

// R
var found = products.FindById(p.Id);
var all = products.FindAll().ToList();
var filtered = products.Find(x => x.Price >= 200m).ToList();
var one = products.FindOne(x => x.Name == "机械键盘");

// U
if (found != null)
{
    found.Stock -= 1;
    products.Update(found);
}

// D
products.Delete(p.Id);

2. 批量插入:优先用 Insert(IEnumerable<T>)

DocumentCollection<T>.Insert(IEnumerable<T>) 内部会分批处理(默认批大小 1000),并走更紧凑的写入路径。

var batch = Enumerable.Range(1, 5000)
    .Select(i => new Product
    {
        Name = $"商品-{i}",
        Price = 10 + i % 100,
        Stock = 1000 - (i % 200)
    })
    .ToList();

var inserted = products.Insert(batch);
Console.WriteLine($"批量插入数量: {inserted}");

3. 批量更新:先查后改,再整体更新

var needPromote = products.Find(x => x.Stock < 50).ToList();

foreach (var item in needPromote)
{
    item.Price *= 0.95m; // 降价促销
}

var updated = products.Update(needPromote);
Console.WriteLine($"批量更新数量: {updated}");

4. 批量删除:两种典型方式

方式 A:按条件删

var deletedLowStock = products.DeleteMany(x => x.Stock == 0);
Console.WriteLine($"删除缺货商品: {deletedLowStock}");

方式 B:按 ID 列表删

var targetIds = products
    .Find(x => x.Price > 500m)
    .Take(200)
    .Select(x => (BsonValue)x.Id)
    .ToList();

var deletedByIds = products.Delete(targetIds);
Console.WriteLine($"按 ID 删除数量: {deletedByIds}");

5. Upsert:插入或更新

当你不确定记录是否存在时,用 Upsert 比自己写 FindById + 分支 更简洁。

var candidate = new Product
{
    Id = ObjectId.NewObjectId(),
    Name = "显示器",
    Price = 1299m,
    Stock = 30
};

var (type1, count1) = products.Upsert(candidate); // 通常是 Insert
Console.WriteLine($"第一次 Upsert: {type1}, {count1}");

candidate.Stock = 28;
var (type2, count2) = products.Upsert(candidate); // 通常是 Update
Console.WriteLine($"第二次 Upsert: {type2}, {count2}");

6. 分页参数建议

Find(predicate, skip, limit) 支持分页,但你应在业务层规范参数:

int page = 1;
int pageSize = 20;
int skip = (page - 1) * pageSize;

var pageData = products.Find(x => x.Price >= 100m, skip, pageSize).ToList();

建议约束:

  • page >= 1

  • 1 <= pageSize <= 500

  • 大页查询优先考虑条件收敛(索引字段)

7. 一段接近生产的仓储封装示例

public sealed class ProductRepository
{
    private readonly ITinyCollection<Product> _col;

    public ProductRepository(TinyDbEngine db)
    {
        _col = db.GetCollection<Product>();
    }

    public ObjectId Add(string name, decimal price, int stock)
    {
        var p = new Product { Name = name, Price = price, Stock = stock };
        _col.Insert(p);
        return p.Id;
    }

    public IReadOnlyList<Product> Search(decimal minPrice, decimal maxPrice, int page, int pageSize)
    {
        var skip = (page - 1) * pageSize;
        return _col.Find(x => x.Price >= minPrice && x.Price <= maxPrice, skip, pageSize).ToList();
    }

    public bool TryReduceStock(ObjectId id, int quantity)
    {
        var p = _col.FindById(id);
        if (p == null || p.Stock < quantity) return false;

        p.Stock -= quantity;
        return _col.Update(p) > 0;
    }

    public int DeleteByIds(IEnumerable<ObjectId> ids)
    {
        return _col.Delete(ids.Select(x => (BsonValue)x));
    }
}

8. 结论

  • 日常开发优先:Insert(IEnumerable)Update(IEnumerable)DeleteMany

  • 对“可能存在”记录用 Upsert

  • 大批量操作时,减少每条日志和每条查询,按批次聚合执行。

下一篇进入查询主题:同一份数据如何做过滤、排序、分页、聚合与分组。