Search code examples
c#mongodbdocumentmongodb-.net-driver

Delete and return document in nested array with mongodb c# driver


I'm having trouble deleting an document in an array using the offical mongodb c# driver. What i'm trying to do is also return the document that was deleted.

Here's what my class looks like:

public class Holder
{
    public string Owner { get; set; }
    public List<Card> Cards { get; set; }
}

public class Card
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Placement { get; set; }
}

And here is an exerpt of the document:

{
  "owner": "656dd33e0300"
  "cards" : [
    {
      "id": "s39CNzu4Na3",
      "name" : "Department",
      "placement" : "8a"
    },
    ...
  ]
}

My first thought was to just get the card and then delete it using the following:

var filter = Builders<Card>.Filter.Eq(x => x.Id, cardId);

var res = await collection.DeleteOneAsync(
     Builders<Holder>.Filter.Eq(e => e.Owner, owner) &
     Builders<Holder>.Filter.ElemMatch(e => e.Cards, filter)
    );
try {            
    return res.DeletedCount > 0;
}
catch (Exception ex) {
    throw;
}

But that just ended up deleting all the cards in the list. I've been trying to use FindOneAndDeleteAsync but i can't get it to work.

Update First a big thanks to both @markus and @yongshun as both their answers worked for me, i have picked @markus as the answer because it was the first answer that worked and i like the shorter code that is similar the rest of my code.


Solution

  • As far as I understand your question, you do not want to delete the root document, but remove a sub-document from an array. You can do this using an update-Statement (delete is for deleting root documents as a whole).

    In order for this to work, you need to create a filter that finds the document and then run an update with a PullFilter on the document.

    In addition, you want to return the sub-document that you deleted during the update. There is no statement to return the sub-document, but you can use FindOneAndUpdateAsync to return the root document in the state before the sub-document was removed:

    var arrayFilter = Builders<Card>.Filter.Eq(x => x.Id, cardId);
    // Find a document with a matching owner that contains at least one card with the specified id
    var docFilter = Builders<Holder>.Filter.Eq(e => e.Owner, owner) 
        & Builders<Holder>.Filter.ElemMatch(e => e.Cards, arrayFilter);
    // Remove all cards from the array that have a matching id 
    // (id should be unique so only one card should be removed)
    var update = Builders<Holder>.Update.PullFilter(e => e.Cards, arrayFilter);
    // Find the document and return it in the state before the update
    var before = await coll.FindOneAndUpdateAsync(
        docFilter,
        update,
        new FindOneAndUpdateOptions<Holder, Holder>() { ReturnDocument = ReturnDocument.Before });
    // Get removed card from the returned document
    var removedCard = before.Cards.SingleOrDefault(x => x.Id == cardId);