MongoDB .NET 如何用 ElemMatch() 匹配并替换字符串数组元素?
结论
字符串数组可以用 ElemMatch() 匹配元素,再用位置运算符 $ 替换第一个匹配的元素。本例中,元素条件写为 s => s == "round",更新路径写为 "MyStringArray.$"。
原代码有两处需要修正:传给 ElemMatch() 的 "round" 会被当作过滤器定义解析;"MyStringArray.[$]" 也不是正确的位置运算符路径。
将 round 替换为 triangle
取得 IMongoCollection<Sample> 类型的 collection 后,可以执行以下更新:
var idFilter = Builders<Sample>.Filter.Eq(
d => d.Id,
"000000000000000000001002");
var elemMatchFilter = Builders<Sample>.Filter.ElemMatch(
d => d.MyStringArray,
s => s == "round");
var filter = Builders<Sample>.Filter.And(
idFilter,
elemMatchFilter);
var update = Builders<Sample>.Update.Set(
"MyStringArray.$",
"triangle");
UpdateResult result = collection.UpdateOne(filter, update);
对于题目中的初始数据,这条更新会将:
["round", "ball"]
改为:
["triangle", "ball"]
过滤器同时限定了文档 ID 和数组元素值。更新路径中的 $ 根据数组匹配条件确定元素位置,因此过滤器中应保留对 MyStringArray 的匹配条件。
为什么直接传入 round 会出现 JSON 解析异常?
问题出在这一行:
Builders<Sample>.Filter.ElemMatch(
x => x.MyStringArray,
"round");
第二个参数需要表示如何匹配数组中的一个元素。传入字符串时,驱动会把它作为 JSON 形式的过滤器定义处理,不会自动将其解释为元素等于这个字符串。
传入解析器的是 round。C# 源代码中包围它的双引号只是字符串字面量的语法,不属于字符串内容,因此会出现:
JSON reader was expecting a value but found 'round'.
改用表达式可以明确指定元素相等条件:
s => s == "round"
单独将更新路径改成 "MyStringArray.$" 无法消除这个异常,因为过滤器解析仍然会失败。
为什么补充代码中的两种写法结果不同?
给出的命令日志显示,两种写法在该环境中生成了不同的查询。
第一种写法:
Builders<string>.Filter.Eq(s => s, "triangle")
放进 ElemMatch() 后,日志中的数组条件是:
{
"MyStringArray": {
"$elemMatch": {
"@<current>": "triangle"
}
}
}
这个条件包含名为 @<current> 的字段,没有生成对字符串元素本身进行比较的 $eq 条件。它没有匹配到给出的字符串数组,日志也记录了 n: 0 和 nModified: 0。
第二种写法直接传入元素判断表达式:
var elemMatchFilter = Builders<Sample>.Filter.ElemMatch(
d => d.MyStringArray,
s => s == "triangle");
日志中的条件为:
{
"MyStringArray": {
"$elemMatch": {
"$eq": "triangle"
}
}
}
它表示数组中至少有一个元素等于 triangle,与预期一致。题目提供的这次调用日志记录了 n: 1 和 nModified: 1。
针对题目所示环境,可以直接使用第二种表达式写法。日志表明两次实际生成的查询不同,但不足以确定这种差异的驱动内部原因、是否属于已知缺陷,或涉及哪些其他版本。
补充代码中的第二种写法还多了一个右括号:原来的 s => s == "triangle")); 应改为 s => s == "triangle");,上面的示例已修正。
如何确认更新结果?
对于已确认写入的操作,可以检查匹配数和修改数:
if (result.IsAcknowledged)
{
Console.WriteLine($"MatchedCount: {result.MatchedCount}");
if (result.IsModifiedCountAvailable)
{
Console.WriteLine($"ModifiedCount: {result.ModifiedCount}");
}
}
排查时可以检查以下几点:
- 初始示例的数组包含
round。先成功将它替换为triangle,后续查找triangle并替换为rectangle的代码才能匹配该元素。 MatchedCount为0时,检查文档 ID、数组当前值以及实际发送的查询条件。- 位置运算符
$替换数组中第一个匹配的元素。如果数组中有多个相同字符串,这段代码不会一次替换全部重复项。 UpdateOne()限制的是更新一条文档;数组中具体更新哪个元素,由更新路径中的位置运算符决定。- 示例中的字符串路径
"MyStringArray.$"假定数据库字段名就是MyStringArray。如果配置了其他 BSON 字段名,需要使用实际存储的字段名。
备注:内容仅供参考。
