How can I upsert a record and array element at the same time?

て烟熏妆下的殇ゞ 提交于 2019-12-02 07:15:23

Took some tinkering, but I got it.

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);

The post linked as being a dupe was absolutely helpful, but it was not the answer. There were a few things that needed to be discovered.

  • The 'second statement' in the linked example didn't work correctly, at least when translated literally. To get it to work, I had to match on the element and then invert the logic by wrapping it in the Not() filter.

  • In order to use 'this index' on the match, you have to use a negative index on the array. As it turns out, the C# driver will convert any negative index to the '$' character when the query is rendered.

  • In order to ensure they are run in order, you must include bulk write options with IsOrdered set to true.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!