C#操作MongoDB不使用_id键时如何避免文档重复插入
现有方案的缺陷
你当前采用的「先查询匹配记录、返回null再插入」的逻辑存在并发竞态问题:当多个携带相同Name/Type/Contact值的写入请求并发执行时,会同时通过「记录不存在」的校验,最终写入多条重复文档,无法可靠保证数据唯一性。
可靠实现方案
要100%避免重复插入,必须从数据库层面加唯一约束,再配合MongoDB的原子写入操作实现,不需要在业务层提前做存在性查询,性能和可靠性都远高于现有方案。
第一步:创建唯一复合索引(核心兜底)
唯一索引是MongoDB层面的强约束,只要索引创建成功,任何违反唯一性规则的写入都会被直接拦截,从根源杜绝重复数据产生。
- Mongo Shell 建索引命令:
db.collectionName.createIndex( { Name: 1, Type: 1, Contact: 1 }, { unique: true } )
- C# 驱动建索引代码:
var col = _mongoCollection.GetCollection<ModelClass>("collectionName"); var keyDef = Builders<ModelClass>.IndexKeys .Ascending(s => s.Name) .Ascending(s => s.Type) .Ascending(s => s.Contact); var indexModel = new CreateIndexModel<ModelClass>(keyDef, new CreateIndexOptions{ Unique = true }); col.Indexes.CreateOne(indexModel);
注意:创建唯一索引前必须先清理集合内已经存在的重复数据,否则索引会创建失败。
第二步:选择原子写入方式替代先查后插
建完索引后,二选一使用以下原子操作即可,不需要提前写查询逻辑:
方式1:直接插入+捕获重复键异常
直接调用InsertOne执行插入:
- 插入成功:说明不存在重复数据,走后续正常逻辑
- 捕获到重复键异常(MongoDB错误码11000):说明已存在相同唯一键的文档,走重复数据处理逻辑
示例代码:
var col = _mongoCollection.GetCollection<ModelClass>("collectionName"); try { col.InsertOne(new ModelClass { Name = A, Type = B, Contact = C, // 其余字段正常赋值 }); // 插入成功后的业务逻辑 } catch (MongoWriteException e) when (e.WriteError.Category == ServerErrorCategory.DuplicateKey) { // 重复数据的处理逻辑,比如返回已存在提示 }
方式2:Upsert原子操作(无异常)
如果不想通过捕获异常做分支判断,可以使用带IsUpsert = true配置的更新操作,用三个唯一字段作为过滤条件,所有待插入字段通过SetOnInsert赋值,保证只有文档不存在时才会执行插入,全程原子执行,不会产生重复,也不会抛重复键异常。
示例代码:
var col = _mongoCollection.GetCollection<ModelClass>("collectionName"); var filter = Builders<ModelClass>.Filter .Eq(s => s.Name, A) & Builders<ModelClass>.Filter.Eq(s => s.Type, B) & Builders<ModelClass>.Filter.Eq(s => s.Contact, C); var update = Builders<ModelClass>.Update .SetOnInsert(s => s.Field4, value4) .SetOnInsert(s => s.Field5, value5); // 所有非唯一标识字段都通过SetOnInsert赋值 col.UpdateOne(filter, update, new UpdateOptions{ IsUpsert = true });
注意必须用
SetOnInsert给非唯一字段赋值,如果用普通Set会在文档已存在时覆盖原有字段值。
内容的提问来源于stack exchange,提问作者Krypto456
相关产品推荐
相关产品推荐

