Mongo更新数组元素(.NET驱动程序2.0)

29

编辑: 我不想使用JavaScript的方法。 我正在寻找使用MongoDB C#2.0驱动程序完成此操作的方法(我知道可能不可能,但我希望有人知道解决方案)。

我正在尝试更新嵌套在mongodb主文档中的数组中项的值。

我正在寻找一种强类型方式来完成这个问题。 我正在使用Mongodb c# 2.0驱动程序

我可以通过弹出元素、更新值然后重新插入来完成它。但这感觉不对,因为我可能会覆盖在此期间写入的内容。

这是我迄今为止尝试过但没有成功的方法:

private readonly IMongoCollection<TempAgenda> _collection;

void Main()
{
    var collectionName = "Agenda";
    var client = new MongoClient("mongodb://localhost:27017");
    var db = client.GetDatabase("Test");
    _collection = db.GetCollection<TempAgenda>(collectionName);
    UpdateItemTitle(1, 1, "hello");
}

public void UpdateItemTitle(string agendaId, string itemId, string title){
    var filter = Builders<TempAgenda>.Filter.Eq(x => x.AgendaId, agendaId);
    var update = Builders<TempAgenda>.Update.Set(x => x.Items.Single(p => p.Id.Equals(itemId)).Title, title);
    var result = _collection.UpdateOneAsync(filter, update).Result;
}

可能是重复的问题:MongoDB - 更新嵌套数组中的对象 - Blakes Seven
3
@BlakesSeven 这不是一个 C# 的问题吗? - Kristian Barrett
这仍然是相同的原则。在查询部分匹配数组元素,并在更新部分使用位置 $ 运算符。我不是随机从袋子里拿出来的。 - Blakes Seven
7
@BlakesSeven 我可以阅读mongo文档并使用纯JavaScript实现,但正如我在问题中所述,我正在寻找强类型的方法来实现这一点。据我所知,在我的C#代码中嵌入JavaScript字符串不是强类型的。 - Kristian Barrett
这是文档:更新嵌入字段 - Blakes Seven
显示剩余2条评论
5个回答

71

花了我一段时间才弄清楚这个问题,因为它似乎没有在任何官方文档(或其他地方)中提到。但是,我在他们的问题跟踪器上找到了这个,其中解释了如何在C# 2.0驱动程序中使用位置运算符$

这应该可以实现你想要的:

public void UpdateItemTitle(string agendaId, string itemId, string title){
    var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
    var update = Builders<TempAgenda>.Update.Set(x => x.Items[-1].Title, title);
    var result = _collection.UpdateOneAsync(filter, update).Result;
}

注意,您的Item.Single()子句已更改为Item.Any()并移动到筛选器定义中。

[-1].ElementAt(-1)显然被特殊处理(实际上是小于0的所有内容)并将被替换为定位符$

以上将转换为此查询:

db.Agenda.update({ AgendaId: 1, Items.Id: 1 }, { $set: { Items.$.Title: "hello" } })

很抱歉回答晚了。它的功能非常好。不幸的是,它们不支持多个操作选择器:https://jira.mongodb.org/browse/SERVER-831 对我来说这很奇怪,因为这可以用于更好地隔离需要进行的更新。 - Kristian Barrett
1
谢谢Søren。不过我必须说,我并不喜欢他们选择的语法,因为这意味着你不能使用IEnumerable<T>或ICollection<T>,而是被迫使用更加繁琐的IList<T>或简单数组。 - Buvy
1
@SørenKruse,你说得没错。我刚验证了一下那个语法,确实非常好用。虽然与数组索引器相比有点繁琐,但至少我不必让所有的域类都使用IList<T>,这正是我想要的。 - Buvy
2
我尝试了这个解决方案,代码实际上编译时没有错误,但在运行时查询文档时,该数组中的[-1]会启动反向索引搜索,而不是使用位置操作符。因此,-1会导致数组的最后一个元素,-2则会给出倒数第二个元素。这并不是我们想要的,索引应通过位置操作符进行动态处理。 - Suleman
在这里执行.Result很可能会导致锁定问题。 - Liam
显示剩余3条评论

2

谢谢,这很有帮助。我还有一个补充,我使用上述方法处理数组时,可以将数据推入嵌套数组和从中拉取。但是我发现问题在于如果我有一个 int 数组(不是对象,只是一个简单的 int 数组),那么 PullFilter 实际上是不起作用的 - "无法确定序列化信息",这很奇怪,因为它只是一个 int 数组。我最终做的是把它变成了一个只有一个 int 参数的对象数组,并且一切都开始正常工作了。可能是一个 bug,或者可能是我的理解有误。无论如何,由于我曾经苦苦寻找关于使用 C# 2.0 驱动程序推入和拉取嵌套对象数组的信息,所以我认为我应该在这里发布我的发现,因为它们使用了上述语法。

var filter = Builders<MessageDto>.Filter.Where(x => x._id == entity.ParentID && x.NestedArray.Any(i => i._id == entity._id));
var update = Builders<MessageDto>.Update.PullFilter(x => x.NestedArray.ElementAt(-1).User, Builders<User>.Filter.Eq(f => f.UserID, userID));
Collection<MessageDto>(currentUser).UpdateOneAsync(filter, update);

并且:
var filter = Builders<MessageDto>.Filter.Where(x => x._id == entity.ParentID && x.NestedArray.Any(i => i._id == entity._id));
var update = Builders<MessageDto>.Update.Push(x => x.NestedArray.ElementAt(-1).Users, new User { UserID = userID });
Collection<MessageDto>(currentUser).UpdateOneAsync(filter, update);

1
你在谈论一个 int 数组,但示例仍然是使用存储复杂结构的数组。请给出一个从 int 数组中删除一个元素的示例。 - EgoPingvina

2
在更新的驱动程序中,ElementAt(-1) 可能不再受支持。当升级到 .NET6MongoDB Driver 2.19.0 后,我曾经使用 (-1) 的代码停止工作。
他们引入了 ExtensionMethods 替代:
x.A.FirstMatchingElement() => "A.$"
x.A.AllElements() => "A.$[]"
x.A.AllMatchingElements("identifier") => "A.$[identifier]"

对于那些感兴趣的人,这已经在https://jira.mongodb.org/browse/CSHARP-4079中有记录。 - Jason Weinzierl

2
这是上述答案的综合工作解决方案,使用了.NET 7MongoDB.Driver 2.20.0 使用FirstMatchingElement()AllElements()代替[-1]
  • AgnendaId是父级的
  • Items是嵌套数组

更新FirstMatchingElement
public void UpdateItemTitle(string agendaId, string itemId, string title)
{
    var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
    var update = Builders<TempAgenda>.Update.Set(x => x.Items.FirstMatchingElement().Title, title);

    _collection.UpdateOneAsync(filter, update);
}

更新 AllElements:
public void UpdateItemTitle(string agendaId, string itemId, string title)
{
    var filter = Builders<TempAgenda>.Filter.Where(x => x.AgendaId == agendaId && x.Items.Any(i => i.Id == itemId));
    var update = Builders<TempAgenda>.Update.Set(x => x.Items.AllElements().Title, title);

    _collection.UpdateOneAsync(filter, update);
}

-1

更新文档或子数组的正确方法如下:

var filter = Builders<Declaracion>.Filter.Where(x => x.Id == di && x.RemuneracionMensualActual.RemuneracionActIndustrial.Any(s => s.Id == oid));

        var update = Builders<Declaracion>.Update.Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).Ingreso, datos.ActividadIndustrial.Ingreso)
            .Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).RazonSocial, datos.ActividadIndustrial.RazonSocial)
            .Set(x => x.RemuneracionMensualActual.RemuneracionActIndustrial.ElementAt(-1).TipoNegocio, datos.ActividadIndustrial.TipoNegocio);

我认为你被踩是因为有人对翻译感到不满意,“正确的方法…”这句话看起来像是“这是唯一正确的方法”,某些人会有反感,并且甚至没有礼貌地留下评论。我要说的是,你的答案确实与上面给出的2个答案重复了,它们使用了[-1].ElementAt(-1) - Colin
请不要灌水。大家都能清楚地看到已经有答案了。 - Ergis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接