MongoDB C#驱动:嵌套数组添加不重复元素失败排查
MongoDB C#驱动嵌套数组元素更新失败排查
问题背景
现有MongoDB文档结构:
[ { "Id": "a399f6c6-13b1-4060-8917-e99c54208165", "Watchlists": [ { "Id": "0fddfa7a-528d-4fa9-9019-e2f7d7699427", "name": "MyWatchList", "Symbols": [ { "SymbolName": "USD", "SymbolId": "0a61be6d-664f-4909-8743-83e202679abf" } ] }, { "Id": "04280f42-0c02-4deb-9c47-4c255efb58d8", "name": "MyWatchList2", "Symbols": [ { "SymbolName": "EURO", "SymbolId": "be2fe40c-e289-4ef6-b43e-28a6e72b296c" } ] } ] } ]
需求:向Watchlists.Symbols中添加新Symbol,要求同一SymbolId的元素不重复添加。
已验证可行的MongoShell更新代码:
db.collection.update({ "Id": "a399f6c6-13b1-4060-8917-e99c54208165" }, { $push: { "Watchlists.$[element].Symbols": { "SymbolName": "USD", "SymbolId": "0a61be6d-664f-4909-8743-83e202679abf" } } }, { arrayFilters: [ { "element.Id": "04280f42-0c02-4deb-9c47-4c255efb58d8", "element.Symbols": { "$not": { "$elemMatch": { "SymbolName": "USD" } } } } ], upsert: true })
转换为C#驱动代码后,无报错但无法添加元素:
var baseFilter = Builders<UserWatchlistSetting>.Filter.Eq(x => x.UserId, userId); var update = Builders<UserWatchlistSetting>.Update .Push("Watchlists.$[d].Symbols", new Symbol { SymbolId = request.SymbolId, Name = request.SymbolName}); var filter1 = new BsonDocument("d.Id", $"{request.watchlistId}"); var filter2 = new BsonDocument("d.Symbols", new BsonDocument("$not", new BsonDocument("$elemMatch", new BsonDocument("SymbolId", $"{request.SymbolId}")))); var arrayFilters = new List<ArrayFilterDefinition> { new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("$and",new BsonArray(){ filter1, filter2})) }; return await mongoCollection.FindOneAndUpdateAsync( baseFilter, update, new FindOneAndUpdateOptions<UserWatchlistSetting> { IsUpsert = true, ReturnDocument = ReturnDocument.After, ArrayFilters = arrayFilters });
错误排查与修正
1. 数组过滤器冗余的$and包裹
MongoDB的arrayFilters中,每个数组元素是一个过滤条件对象,多个键值对默认是逻辑与关系,无需显式用$and包裹。原代码中的$and会导致过滤条件无法正确匹配,进而跳过更新。
修正后的数组过滤器写法:
// 直接将两个条件合并到同一个BsonDocument中 var arrayFilter = new BsonDocument { { "d.Id", request.watchlistId }, { "d.Symbols", new BsonDocument("$not", new BsonDocument("$elemMatch", new BsonDocument("SymbolId", request.SymbolId))) } }; var arrayFilters = new List<ArrayFilterDefinition> { new BsonDocumentArrayFilterDefinition<BsonDocument>(arrayFilter) };
2. 实体类字段映射不匹配
Mongo文档中Symbol子文档的字段是SymbolName,但C#代码中Symbol类的Name属性如果没有显式指定Bson元素名,序列化后会变成"Name",与文档字段不匹配。同时需确保UserWatchlistSetting和Watchlist类的Id字段与文档中的大写Id一致。
修正Symbol类的属性映射:
public class Symbol { [BsonElement("SymbolId")] public string SymbolId { get; set; } [BsonElement("SymbolName")] public string Name { get; set; } } // 同时确保Watchlist类的Id字段映射正确 public class Watchlist { [BsonElement("Id")] public string Id { get; set; } [BsonElement("name")] public string Name { get; set; } public List<Symbol> Symbols { get; set; } } public class UserWatchlistSetting { [BsonElement("Id")] public string UserId { get; set; } public List<Watchlist> Watchlists { get; set; } }
3. 推荐使用强类型路径替代字符串硬编码
使用强类型API可以避免字符串路径拼写错误,提升代码可维护性:
// 强类型的Update定义 var update = Builders<UserWatchlistSetting>.Update .Push(x => x.Watchlists[-1].Symbols, new Symbol { SymbolId = request.SymbolId, Name = request.SymbolName }); // 强类型的数组过滤器(更推荐) var arrayFilter = Builders<Watchlist>.Filter.Eq(w => w.Id, request.watchlistId) & Builders<Watchlist>.Filter.Not(Builders<Watchlist>.Filter.ElemMatch(w => w.Symbols, s => s.SymbolId == request.SymbolId)); var arrayFilters = new List<ArrayFilterDefinition> { new ArrayFilterDefinition<Watchlist>(arrayFilter) };
最终修正后的完整代码
// 确保实体类字段映射正确(参考上述代码) var baseFilter = Builders<UserWatchlistSetting>.Filter.Eq(x => x.UserId, userId); var update = Builders<UserWatchlistSetting>.Update .Push(x => x.Watchlists[-1].Symbols, new Symbol { SymbolId = request.SymbolId, Name = request.SymbolName }); var arrayFilter = Builders<Watchlist>.Filter.Eq(w => w.Id, request.watchlistId) & Builders<Watchlist>.Filter.Not(Builders<Watchlist>.Filter.ElemMatch(w => w.Symbols, s => s.SymbolId == request.SymbolId)); var arrayFilters = new List<ArrayFilterDefinition> { new ArrayFilterDefinition<Watchlist>(arrayFilter) }; return await mongoCollection.FindOneAndUpdateAsync( baseFilter, update, new FindOneAndUpdateOptions<UserWatchlistSetting> { IsUpsert = true, ReturnDocument = ReturnDocument.After, ArrayFilters = arrayFilters });
内容的提问来源于stack exchange,提问作者Alexandr61region1
相关产品推荐
相关产品推荐

