最新版 MongoDB.Driver(2.20+)配合强类型 POCO 类是最稳路径;必须用 IMongoClient 单例 + 泛型 IMongoCollection<T>,避免同步写法、BsonDocument/dynamic 误用,注意 InsertOneAsync/UpdateOne 语义差异、FilterDefinition 正确构建及异步调用规范。

用最新版 MongoDB.Driver(2.20+)配合强类型 POCO 类,是当前最稳的路径;硬套老教程里的 Insert、Find 同步写法,或滥用 BsonDocument / dynamic,90% 会掉进序列化、空引用、过滤失效的坑里。
连接与集合初始化必须用 IMongoClient 单例 + 泛型 IMongoCollection<T>
别在每次请求里 new MongoClient(),也别用 GetCollection("users") 返回裸 IMongoCollection。.NET 6/7/8 下必须走 DI 注册单例,并指定泛型类型:
services.AddSingleton<IMongoClient>(sp => new MongoClient(connectionString));services.AddScoped<IMongoCollection<User>>(sp => sp.GetRequiredService<IMongoClient>().GetDatabase("mydb").GetCollection<User>("users"));
泛型 T 决定反序列化行为:字段缺失时,int 会默认为 0,string 为 null,但不会报错——查不到数据时先检查文档里字段是否存在、大小写是否匹配(MongoDB 默认区分大小写)。
InsertOneAsync 和 UpdateOne 的语义陷阱:不是“插入”和“更新”,而是“插入整个文档”和“替换整个文档”
常见错误是传一个不带 _id 的 User 实体进 UpdateOne,结果 MongoDB 把原文档删了,新建一个带新 _id 的文档——因为 UpdateOne 默认不做局部更新。
- 要局部更新(比如只改
Status),必须用Builders<User>.Update.Set(u => u.Status, "inactive") -
InsertOneAsync后想拿生成的 ID,别再new ObjectId(),直接取result.InsertedId(类型是ObjectId,不是string) - 如果实体类里有
[BsonId]属性,驱动会自动处理;没加的话,_id字段必须显式赋值,否则插入后InsertedId是空
查询过滤器写不对,等于没写:FilterDefinition<T> 不是 JSON 字符串,也不是裸 BsonDocument
collection.Find("{status: 'active'}") 看似简洁,实则无效——它会被当作文本字符串忽略,最终返回全量数据或空(取决于驱动版本)。真正生效的写法只有两种:
- 用表达式构建:
Builders<User>.Filter.Eq(u => u.Status, "active") & Builders<User>.Filter.Gt(u => u.CreatedAt, DateTime.UtcNow.AddDays(-7)) - 用 JSON 解析:
FilterDefinition<User>.Parse("{status: 'active', createdAt: {$gt: { $date: '2026-04-13T00:00:00Z' }}}")(注意日期格式必须 ISO 8601)
用 FindAsync 后别急着 ToListAsync():只取一条就用 FirstOrDefaultAsync(),它自带 .Limit(1),服务端提前终止游标,省带宽也省时间。
DeleteOne 返回值不是布尔值,DeletedCount 才是真相
删除操作不抛异常 ≠ 文档被删了。常见误判是只写 await collection.DeleteOneAsync(filter) 就完事,结果 DeletedCount == 0 却没察觉。
- 务必检查返回值:
var result = await collection.DeleteOneAsync(filter); if (result.DeletedCount == 0) throw new InvalidOperationException("No document matched filter"); - ID 过滤必须用
new ObjectId(id),传字符串进去永远匹配失败 - 生产环境删之前先
CountDocumentsAsync(filter)预估数量,避免误删整表;删完记得清缓存或发消息通知下游
最易被忽略的一点:所有异步方法(InsertOneAsync、FindAsync、DeleteOneAsync)都要求调用链全程 async/await,用 .Result 或 .Wait() 在 ASP.NET Core 中大概率引发死锁——这不是配置问题,是运行时调度机制决定的。


















