【问题标题】:How can I upsert a record and array element at the same time?如何同时更新记录和数组元素?
【发布时间】:2017-08-05 00:49:22
【问题描述】:

这意味着被读取为双重 upsert 操作,先更新文档,然后再插入数组元素。

所以 MongoDB 对我来说是一个非规范化的存储(我们是事件来源的),我试图处理的事情之一就是它的并发性。问题是这样的:

  • 事件可能出现乱序,因此对数据库的每次更新都需要更新插入。
  • 我不仅需要更新父文档,还需要更新该文档的数组属性中的元素。

例如:

  • 如果文档不存在,请创建它。此流中的所有事件都有文档的 ID,但只有部分信息取决于事件。
  • 如果文档确实存在,则更新它。这是简单的部分。更新命令只是写为 UpdateOneAsync 和 upsert。
  • 如果事件实际上是更新列表,则需要更新列表元素。因此,如果文档不存在,则需要创建它并且列表项将被更新插入(导致插入);如果文档确实存在,那么我们需要找到元素并将其更新为 upsert,因此如果元素存在则更新它,否则它被插入。

如果可能的话,让它作为单个原子操作执行将是理想的,但如果它只能分多个步骤完成,那就这样吧。由于 2.x 驱动程序的巨大变化,我在网上得到了许多混合示例。不确定我在寻找 UpdateOneAsync 之外的什么。当前使用 2.4.x。解释的例子将不胜感激。 TIA

注意: 重申这是关于 MongoDB C# 驱动程序 2.4.x 的问题

【问题讨论】:

  • @neillunn RE:重复的问题:这是用 mongocsharpdriver 标记的,并在帖子中提到了驱动程序版本。另一个答案是 mongo cli。我会更新帖子以更清楚地说明这一点。
  • 我会看看我是否不能理解它背后的理论并将其应用于 C# 驱动程序的使用,但我只是指出这不是一个骗局。
  • 我同意,但是 C# 驱动程序实现了一个非常复杂的接口,这是“我如何使用 C# 驱动程序做到这一点”问题的一部分。这不是我是否希望有人为我编写代码的问题,而是不知道使用这个特定版本的驱动程序要使用哪些线程。当人们在这种假设下解决这个问题时,这是令人难以置信的恶化。我不知道我不知道什么。富有成效的答案可能是参考其他帖子,然后讨论 C# 驱动程序如何实现这一点,因为对于一些新手来说并不那么明显。
  • 这不应该是关于你是否认为我应该能够通过了解如何在 mongo CLI 中完成它来回答我自己关于 C# 驱动程序的问题,而是关于是否并不是说它不是一个重复的问题。
  • 我不理解驱动程序界面,尼尔。我正在谷歌搜索试图找到一个完整的例子,甚至使用它,但我遇到的一切似乎都跳过了至少一半的步骤,甚至创建执行这 3 个步骤的模型。换句话说,我想使用您的文章,但将其插入 C# 驱动程序被证明是一个挑战。

标签: mongodb mongodb-.net-driver event-sourcing


【解决方案1】:

花了点功夫,但我明白了。

var notificationData = new NotificationData
{
    ReferenceId = e.ReferenceId,
    NotificationId = e.NotificationId,
    DeliveredDateUtc = e.SentDate.DateTime
};

var matchDocument = Builders<SurveyData>.Filter.Eq(s => s.SurveyId, e.EntityId);

// first upsert the document to make sure that you have a collection to write to
var surveyUpsert = new UpdateOneModel<SurveyData>(
    matchDocument,
    Builders<SurveyData>.Update
        .SetOnInsert(f => f.SurveyId, e.EntityId)
        .SetOnInsert(f => f.Notifications, new List<NotificationData>())){ IsUpsert = true};

// then push a new element if none of the existing elements match
var noMatchReferenceId = Builders<SurveyData>.Filter
    .Not(Builders<SurveyData>.Filter.ElemMatch(s => s.Notifications, n => n.ReferenceId.Equals(e.ReferenceId)));

var insertNewNotification = new UpdateOneModel<SurveyData>(
    matchDocument & noMatchReferenceId,
    Builders<SurveyData>.Update
        .Push(s => s.Notifications, notificationData));

// then update the element that does match the reference ID (if any)
var matchReferenceId = Builders<SurveyData>.Filter
    .ElemMatch(s => s.Notifications, Builders<NotificationData>.Filter.Eq(n => n.ReferenceId, notificationData.ReferenceId));
var updateExistingNotification = new UpdateOneModel<SurveyData>(
    matchDocument & matchReferenceId,
    Builders<SurveyData>.Update 
        // apparently the mongo C# driver will convert any negative index into an index symbol ('$')
        .Set(s => s.Notifications[-1].NotificationId, e.NotificationId)
        .Set(s => s.Notifications[-1].DeliveredDateUtc, notificationData.DeliveredDateUtc));

// execute these as a batch and in order
var result = await _surveyRepository.DatabaseCollection
    .BulkWriteAsync(
        new []{ surveyUpsert, insertNewNotification, updateExistingNotification }, 
        new BulkWriteOptions { IsOrdered = true })
    .ConfigureAwait(false);

链接为骗子的帖子绝对有帮助,但这不是答案。有几件事需要被发现。

  • 链接示例中的“第二条语句”不起作用 正确,至少在字面翻译时。为了让它工作,我必须匹配 元素,然后通过将其包装在 Not() 过滤器中来反转逻辑。

  • 为了在比赛中使用“这个索引”,你必须使用 数组上的负索引。事实证明,C# 驱动程序将 查询时将任何负索引转换为“$”字符 渲染。

  • 为了确保它们按顺序运行,您必须包括批量写入 IsOrdered 设置为 true 的选项。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-07
    • 2020-12-09
    • 1970-01-01
    • 2018-03-11
    • 2015-12-05
    • 1970-01-01
    相关资源
    最近更新 更多