如何同时插入记录和数组元素?

时间:2017-08-01 23:29:42

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

这意味着要作为双重upsert操作读取,然后将文档插入数组元素。

所以MongoDB对我来说是一个非规范化的商店(我们的事件来源),而我试图处理的其中一件事就是它的并发性。问题是:

  • 事件可能不按顺序进行,因此每次更新数据库都必须是一个upsert。
  • 我需要不仅能够在父文档中插入,而且还能在该文档的数组属性中使用元素。

例如:

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

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

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

1 个答案:

答案 0 :(得分: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);

作为一个骗局链接的帖子绝对有帮助,但它不是答案。有一些事情需要发现。

  • 第二个陈述&#39;在链接的例子中没有工作 正确的,至少在字面翻译时。为了让它发挥作用,我不得不匹配 元素然后通过将其包装在Not()过滤器中来反转逻辑。

  • 为了使用&#39;这个索引&#39;在比赛中,你必须使用一个 数组上的负索引。事实证明,C#驱动程序会 将任何否定索引转换为&#39; $&#39;查询时的字符 呈现。

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