.NET使用MongoDB更新文档触发不可变字段_id错误问题问询
报错根因
你调用的FindOneAndReplaceAsync属于全量文档替换逻辑,传入的record对象要么存在未赋值的Id属性(默认值为Guid.Empty,序列化后对应报错里的全0 BinData),要么没有携带和过滤条件一致的_id字段。MongoDB执行全量替换时会用新传入的record结构覆盖原文档所有字段,导致原文档的不可变字段_id被修改,触发报错。
该报错和MongoDB Atlas分片部署无关,属于MongoDB通用的不可变字段修改校验逻辑。
解决方案1:全量替换适配(改动最小)
首先给实体类的Id字段加上Bson映射,再在Upsert方法内主动给record的Id赋值,保证替换后_id和过滤条件完全一致:
// 实体类示例,给Id加映射标记 public class TestObj { [BsonId] // 标记该字段映射MongoDB的_id内置字段 public Guid Id { get; set; } public string name { get; set; } public int total { get; set; } }
修改后的Upsert方法:
public T UpsertRecord<T>(string table, Guid id, T record) where T : class { var collection = db.GetCollection<T>(table); // 反射给record的Id字段赋值,和过滤条件的_id保持一致 var idProp = typeof(T).GetProperty("Id"); if (idProp != null && idProp.CanWrite) { idProp.SetValue(record, id); } var filter = Builders<T>.Filter.Eq("_id", id); var result = collection.FindOneAndReplaceAsync<T>(filter, record, new FindOneAndReplaceOptions<T, T> { IsUpsert = true }).GetAwaiter().GetResult(); return record; }
解决方案2:部分更新(更匹配需求,不会触碰_id字段)
放弃全量替换,自动把record的所有非Id字段转为更新操作,完全不会修改_id字段,天然符合“仅更新除_id外其余字段”的需求,且不需要修改原有实体类:
public T UpsertRecord<T>(string table, Guid id, T record) { var collection = db.GetCollection<T>(table); var filter = Builders<T>.Filter.Eq("_id", id); // 自动生成除Id外所有字段的更新定义 var update = Builders<T>.Update.Combine( typeof(T).GetProperties() .Where(p => p.Name != "Id") // 显式排除Id字段,避免修改_id .Select(p => Builders<T>.Update.Set(p.Name, p.GetValue(record))) ); var options = new FindOneAndUpdateOptions<T, T> { IsUpsert = true, // 不存在时自动插入新文档,新文档_id会自动使用过滤条件的id ReturnDocument = ReturnDocument.After // 返回操作后的最新文档 }; return collection.FindOneAndUpdateAsync(filter, update, options).GetAwaiter().GetResult(); }
内容的提问来源于stack exchange,提问作者Timmie Nilsson
相关产品推荐
相关产品推荐

