skip to Main Content

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.

2

Answers


  1. From your code, you are deleting the entire Holder document but not removing the Card element from the Cards array in the Holder document.

    I don’t think the MongoDB .NET driver supports removing and returning the deleted element (API). Thus, you should:

    1. Filter the document and get the filtered Card elements.

    2. Pull the Card element(s) which is matched from the Cards array.

    3. Return res.ModifiedCount > 0 indicates the card element is removed and the items removed.

    var filter = Builders<Card>.Filter.Eq(x => x.Id, cardId);
    
    var holder = await collection.Find(
            Builders<Holder>.Filter.Eq(e => e.Owner, owner) &
            Builders<Holder>.Filter.ElemMatch(e => e.Cards, filter))
        .Project<Holder>(Builders<Holder>.Projection
            .Include(x => x.Owner)
            .ElemMatch(x => x.Cards, filter))
        .FirstOrDefaultAsync();
    
    if (holder == null || !holder.Cards.Any())
    {
        return new
        {
            IsCardDeleted = false,
            DeletedCards = (List<Card>)null
        };
    }
    
    UpdateResult res = await collection.UpdateOneAsync(
         Builders<Holder>.Filter.Eq(e => e.Owner, owner) &
         Builders<Holder>.Filter.ElemMatch(e => e.Cards, filter),
         Builders<Holder>.Update.PullFilter(e => e.Cards, filter)
    );
    
    if (res.ModifiedCount == 0)
    {
        return new
        {
            IsCardDeleted = false,
            DeletedCards = (List<Card>)null
        };
    }
    
    return new
    {
        IsCardDeleted = true,
        DeletedCards = holder.Cards
    };
    

    enter image description here

    Login or Signup to reply.
  2. 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);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search