✦ LiteDB 原始 API 简介(LiteDB Original API Overview)

[LiteDB] 是一个轻量级嵌入式 [[NoSQL]] 数据库,核心入口是 LiteDatabase 类,通过连接字符串初始化,并需要手动释放:

1
2
using var db = new LiteDatabase(@"Filename=app.db;Connection=direct");
var collection = db.GetCollection<User>("users");

ILiteCollection<T> 提供了完整的 [[CRUD]] 方法:

方法 作用 说明
FindById(BsonValue id) 按 ID 查询 ID 类型为 [[BsonValue]]
Find(Expression<Func<T, bool>> predicate) 按谓词查询 返回 IEnumerable<T>
Insert(T entity) / Insert(IEnumerable<T>) 插入 返回 BsonValue / int
InsertBulk(IEnumerable<T>, int batchSize) 批量插入 带批次参数
Update(T entity) / Update(IEnumerable<T>) 更新 返回 bool / int
Upsert(T entity) / Upsert(BsonValue id, T entity) 新增或更新 返回 bool
UpdateMany(BsonExpression transform, BsonExpression predicate) 批量表达式更新 参数需构造 BsonExpression 对象
EnsureIndex(string name, BsonExpression expression, bool unique) 创建索引 同样需构造 BsonExpression
DropIndex(string name) 删除索引
Delete(BsonValue id) 按 ID 删除 返回 bool
DeleteMany(Expression<Func<T, bool>> predicate) 批量删除 返回 int

三个明显的痛点:

  • UpdateManyEnsureIndex 暴露了 [[BsonExpression]]——调用方需要了解 LiteDB 内部 DSL 和 BsonExpression.Create() 的调用方式
  • 所有方法不内置日志,异常排查必须在外层逐一包裹 try-catch
  • LiteDatabase 的生命周期需手动管理,与 [[DependencyInjection]] 容器配合不够自然

✦ 为什么封装(Why Wrap)

✦ 减少冗余与统一日志(Less Boilerplate, Unified Logging)

不使用封装时,每次 CRUD 调用的代码模式高度重复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 每个实体每调一次 CRUD 都要写一遍 try-catch + 日志
try
{
var db = new LiteDatabase(connectionString);
var col = db.GetCollection<User>("users");
_logger.LogDebug("查询 User,ID: {Id}", id);
return col.FindById(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "查询 User 失败,ID: {Id}", id);
throw;
}
finally
{
db.Dispose();
}

封装后,统一模板只需写一次——每个方法在父类中写一次 try-catch + 结构化日志,所有实体共享,消除重复。

✦ 隐藏底层实现细节(Hiding Implementation Details)

原始 ILiteCollection<T>.UpdateMany 接收 [[BsonExpression]] 参数:

1
2
3
4
5
// LiteDB 原生方法
collection.UpdateMany(
BsonExpression.Create("$.Name = UPPER($.Name)"),
BsonExpression.Create("$.Age > 18")
);

封装后将 BsonExpression.Create() 的调用内化:

1
2
// 封装后——调用方只需传字符串
int UpdateMany(string transform, string predicate);

内部实现自动完成 BsonExpression.Create(),调用方不感知 BsonExpression 的存在。EnsureIndex(string, string, bool) 同理。

这带来了两个好处:

  • 调用方 API 签名更简洁
  • 解除了对 LiteDB 内部类型的直接依赖

✦ 复用与面向接口编程(Reusability & Interface-Oriented Programming)

这正是 [[RepositoryPattern]] 的核心实践。IRepository<TEntity> 定义统一的数据访问契约,业务层只依赖接口:

1
2
3
4
5
6
7
8
9
public class SomeService
{
private readonly IRepository<AppConfigModel> _configRepo;

public SomeService(IRepository<AppConfigModel> configRepo)
{
_configRepo = configRepo;
}
}

好处:

  • 可 Mock 测试
  • 可替换存储后端(只需实现 IRepository<T>
  • 与 [[DependencyInjection]] 容器无缝集成(Scoped / Singleton)

✦ 生命周期管理(Lifecycle Management)

LiteDbContext 封装了 LiteDatabase 的初始化与释放,注入 DI 容器后由容器自动管理生命周期,业务代码完全不关心数据库连接的创建与销毁。

✦ 代码展示(Code Showcase)

✦ 实体基础契约 — IEntity<TId>(Entity Contract: IEntity<TId>)

1
2
3
4
5
6
namespace Arturia.Core.Storage;

public interface IEntity<TId>
{
public TId Id { get; set; }
}

✦ 仓储抽象接口 — IRepository<TEntity>(Repository Interface: IRepository<TEntity>)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System.Linq.Expressions;
using LiteDB;

namespace Arturia.Core.Storage;

public interface IRepository<TEntity> where TEntity : class, IEntity<Guid>
{
public TEntity FindById(Guid id);
public TEntity? FindFirstOrDefault(Expression<Func<TEntity, bool>> predicate);
public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);

public bool Upsert(TEntity entity);
public int Upsert(IEnumerable<TEntity> entities);
public bool Upsert(Guid id, TEntity entity);

public bool Update(TEntity entity);
public bool Update(Guid id, TEntity entity);
public int Update(IEnumerable<TEntity> entities);

public int UpdateMany(string transform, string predicate);

public Guid Insert(TEntity entity);
public int Insert(IEnumerable<TEntity> entities);
public int InsertBulk(IEnumerable<TEntity> entities, int batchSize = 5000);

public bool EnsureIndex(Expression<Func<TEntity, bool>> expression, bool unique = false);
public bool EnsureIndex(string name, string expression, bool unique = false);

public bool DropIndex(string name);

public bool Delete(Guid id);
public int DeleteMany(Expression<Func<TEntity, bool>> predicate);
}

✦ 数据库连接封装 — LiteDbContext(Database Context: LiteDbContext)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using LiteDB;
using Microsoft.Extensions.Logging;

namespace Arturia.Core.Storage;

public class LiteDbContext : IDisposable
{
public LiteDatabase Database { get; }
private readonly ILogger<LiteDbContext> _logger;

public LiteDbContext(string connectionString, ILogger<LiteDbContext> logger)
{
_logger = logger;
try
{
Database = new LiteDatabase(connectionString);
_logger.LogDebug("LiteDB database initialized.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize LiteDB database.");
throw;
}
}

public void Dispose()
{
Database.Dispose();
_logger.LogDebug("LiteDbContext disposed.");
}
}

✦ 仓储实现 — LiteDbRepository<TEntity>(Repository Implementation: LiteDbRepository<TEntity>)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
using System.Linq.Expressions;
using LiteDB;
using Microsoft.Extensions.Logging;

namespace Arturia.Core.Storage;

public class LiteDbRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity<Guid>
{
private readonly ILiteCollection<TEntity> _collection;
private readonly ILogger<LiteDbRepository<TEntity>> _logger;
private readonly string _entityName;

public LiteDbRepository(LiteDbContext context, ILogger<LiteDbRepository<TEntity>> logger)
{
_logger = logger;
_entityName = typeof(TEntity).Name;

var db = context.Database;

string collectionName = $"{typeof(TEntity).Name.ToLower()}s";
_collection = db.GetCollection<TEntity>(collectionName);
}

public TEntity FindById(Guid id)
{
try
{
_logger.LogDebug("查询{Entity},ID: {Id}", _entityName, id);
return _collection.FindById(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "查询{Entity}失败,ID: {Id}", _entityName, id);
throw;
}
}

public TEntity? FindFirstOrDefault(Expression<Func<TEntity, bool>> predicate)
{
try
{
_logger.LogDebug("查询{Entity},谓词: {Predicate}", _entityName, predicate);
return _collection.Find(predicate).FirstOrDefault();
}
catch (Exception ex)
{
_logger.LogError(ex, "查询{Entity}失败,谓词: {Predicate}", _entityName, predicate);
throw;
}
}

public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
try
{
_logger.LogDebug("查询{Entity}列表,谓词: {Predicate}", _entityName, predicate);
return _collection.Find(predicate);
}
catch (Exception ex)
{
_logger.LogError(ex, "查询{Entity}列表失败,谓词: {Predicate}", _entityName, predicate);
throw;
}
}

public bool Upsert(TEntity entity)
{
try
{
_logger.LogDebug("新增或更新{Entity},ID: {Id}", _entityName, entity.Id);
return _collection.Upsert(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "新增或更新{Entity}失败,ID: {Id}", _entityName, entity.Id);
throw;
}
}

public int Upsert(IEnumerable<TEntity> entities)
{
var list = entities.ToList();
try
{
_logger.LogDebug("批量新增或更新{Entity},共{Count}条", _entityName, list.Count);
return _collection.Upsert(list);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量新增或更新{Entity}失败,共{Count}条", _entityName, list.Count);
throw;
}
}

public bool Upsert(Guid id, TEntity entity)
{
try
{
_logger.LogDebug("新增或更新{Entity},ID: {Id}", _entityName, id);
return _collection.Upsert(id, entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "新增或更新{Entity}失败,ID: {Id}", _entityName, id);
throw;
}
}

public bool Update(TEntity entity)
{
try
{
_logger.LogDebug("更新{Entity},ID: {Id}", _entityName, entity.Id);
return _collection.Update(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "更新{Entity}失败,ID: {Id}", _entityName, entity.Id);
throw;
}
}

public bool Update(Guid id, TEntity entity)
{
try
{
_logger.LogDebug("更新{Entity},ID: {Id}", _entityName, id);
return _collection.Update(id, entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "更新{Entity}失败,ID: {Id}", _entityName, id);
throw;
}
}

public int Update(IEnumerable<TEntity> entities)
{
var list = entities.ToList();
try
{
_logger.LogDebug("批量更新{Entity},共{Count}条", _entityName, list.Count);
return _collection.Update(list);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量更新{Entity}失败,共{Count}条", _entityName, list.Count);
throw;
}
}

public int UpdateMany(string transform, string predicate)
{
if (string.IsNullOrEmpty(transform))
throw new ArgumentNullException(nameof(transform));
if (string.IsNullOrEmpty(predicate))
throw new ArgumentNullException(nameof(predicate));

try
{
_logger.LogDebug("批量更新{Entity},转换: {Transform},谓词: {Predicate}", _entityName, transform, predicate);

BsonExpression bsonTransform = BsonExpression.Create(transform);
BsonExpression bsonPredicate = BsonExpression.Create(predicate);

return _collection.UpdateMany(bsonTransform, bsonPredicate);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量更新{Entity}失败,转换: {Transform},谓词: {Predicate}", _entityName, transform, predicate);
throw;
}
}

public Guid Insert(TEntity entity)
{
try
{
_logger.LogDebug("插入{Entity}", _entityName);
return _collection.Insert(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "插入{Entity}失败", _entityName);
throw;
}
}

public int Insert(IEnumerable<TEntity> entities)
{
var list = entities.ToList();
try
{
_logger.LogDebug("批量插入{Entity},共{Count}条", _entityName, list.Count);
return _collection.Insert(list);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量插入{Entity}失败,共{Count}条", _entityName, list.Count);
throw;
}
}

public int InsertBulk(IEnumerable<TEntity> entities, int batchSize = 5000)
{
var list = entities.ToList();
try
{
_logger.LogDebug("批量插入{Entity},共{Count}条,批次大小: {BatchSize}", _entityName, list.Count, batchSize);
return _collection.InsertBulk(list, batchSize);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量插入{Entity}失败,共{Count}条,批次大小: {BatchSize}", _entityName, list.Count, batchSize);
throw;
}
}

public bool EnsureIndex(Expression<Func<TEntity, bool>> expression, bool unique = false)
{
try
{
_logger.LogDebug("确保索引{Entity},表达式: {Expression},唯一: {Unique}", _entityName, expression, unique);
return _collection.EnsureIndex(expression, unique);
}
catch (Exception ex)
{
_logger.LogError(ex, "确保索引{Entity}失败,表达式: {Expression},唯一: {Unique}", _entityName, expression, unique);
throw;
}
}

public bool EnsureIndex(string name, string expression, bool unique = false)
{
try
{
_logger.LogDebug("确保索引{IndexName},表达式: {Expression},唯一: {Unique}", name, expression, unique);

BsonExpression bsonExpression = BsonExpression.Create(expression);
return _collection.EnsureIndex(name, bsonExpression, unique);
}
catch (Exception ex)
{
_logger.LogError(ex, "确保索引{IndexName}失败,表达式: {Expression},唯一: {Unique}", name, expression, unique);
throw;
}
}

public bool DropIndex(string name)
{
try
{
_logger.LogDebug("删除索引{IndexName}", name);
return _collection.DropIndex(name);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除索引{IndexName}失败", name);
throw;
}
}

public bool Delete(Guid id)
{
try
{
_logger.LogDebug("删除{Entity},ID: {Id}", _entityName, id);
return _collection.Delete(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除{Entity}失败,ID: {Id}", _entityName, id);
throw;
}
}

public int DeleteMany(Expression<Func<TEntity, bool>> predicate)
{
try
{
_logger.LogDebug("批量删除{Entity},谓词: {Predicate}", _entityName, predicate);
return _collection.DeleteMany(predicate);
}
catch (Exception ex)
{
_logger.LogError(ex, "批量删除{Entity}失败,谓词: {Predicate}", _entityName, predicate);
throw;
}
}
}

✦ 总结(Summary)

封装让业务层只面对 IRepository<T> 这一抽象接口,无需关心 BsonExpressionLiteDatabase 生命周期、日志埋点等底层细节。统一的 try-catch 模板、隐藏的 BsonExpression.Create() 调用、与 DI 容器的自然集成——每一次 CRUD 操作都不再是碎片化的裸调用,而是走一条标准化、可观测的数据通道。