0

I want to remove one field from every document using MongoDB driver for C#.

I have documents like this:

{
  "_id": ObjectId("554e05dfc90d3d4dfcaa2aea"),
  "username": "abc",
  "someArray": [
    {
      "_id": ObjectId("554e0625a51586362c33c6df"),
      "IsReadOnly": false
    },
    {
      "_id": ObjectId("554e0625a51586362c33c6df"),
      "IsReadOnly": true
    }
  ]
} 

I want to remove the field "IsReadOnly" from "someArray", regardless of whether the value is true or false.

In MongoDB I can reach that with the following script:

db.getCollection('Collection').find({}).forEach( function(doc) {
  var arr = doc.someArray;
  var length = arr.length;
  for (var i = 0; i < length; i++) {
    delete arr[i]["IsReadOnly"];
  }
  db.getCollection('Collection').save(doc);
});

In C# I tried the following:

var update= Update.Pull("someArray",BsonValue.Create("IsReadOnly"));

// or: var update = Update.Unset("someArray.$[].IsReadOnly");
myDb.Collection.Update(null, update, UpdateFlags.Multi);

But the "IsReadOnly" wasn't deleted. How can I delete this field from every array and every document?

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • https://docs.mongodb.com/manual/reference/operator/update/unset/ – Chris Feb 06 '19 at 09:08
  • as mentioned in the question, I tried to use unset, but it didn't delete the field – OldTimeRambler Feb 06 '19 at 09:20
  • Ah, sorry, I missed it in the comment. Just to check that syntax looks at a glance like the 1.x drivers - is that correct? Also I just checked with a test directly in mongo shell and the following did what you wanted: `db.getCollection('TestCollection').update({}, {$unset: {"someArray.$[].IsReadOnly": ""}})`. I assume you are also on version 3.6 or higher of MongoDB which is where `$[]` was introduced? – Chris Feb 06 '19 at 09:31
  • `db.version()` print 2.0.4. Driver version is 1.4.0.4468. I tried the `$[]` syntax in mongo shell, but it didn't work (as expected, because, as you said, the syntax was introduced in 3.6) – OldTimeRambler Feb 06 '19 at 09:38
  • I've not got the 1.4 drivers any more nor a db version that old. I do seem to recall though that just `"someArray.IsReadOnly"` was sufficient for querying. Perhaps try it like that to see if it will match. If not I'm out of ideas and not in a position to mess around with it. :( – Chris Feb 06 '19 at 10:03

4 Answers4

1

In the Mongo Query Language the easiest way you can express this update is using the $[] (Positional All) operator. It looks something like this:

db.col.update({},{$unset:{"someArray.$[].IsReadOnly":1}},{multi:true})

However, this operator has not been explicitly implemented in the strongly typed Builders available in the C# Driver.

Since this operator is missing you have to build the update "by hand". Here's a quick sample.

var mongo = new MongoClient();
var db = mongo.GetDatabase("test");
var col = db.GetCollection<BsonDocument>("col");
await col.UpdateManyAsync(new BsonDocument(), Builders<BsonDocument>.Update.Unset("someArray.$[].IsReadOnly"));

There are some potential pitfalls here with field naming if you have some custom serialization settings (such as lower camel cased field names), so be sure to test things.

It is worth noting that this answer is contingent upon you running a version of MongoDB that is 3.6 or newer. If you are running an older version, this method will not work as the $[] operator does not exist in earlier versions.

Pete Garafano
  • 4,863
  • 1
  • 23
  • 40
0

Hi Have you tried with MongoDb.Driver.Linq .. with something like:

var myEntity = myDb.Collection.AsQueryable().Where(xx=> xx.MyProp == somthing).FirstOrDefault(); //<-- to Extract your entity with Array Inside

then ..

var myArray = myEntity.ArrayNested.Where(xx=> xx.prop != conditiontoMatchElement).ToList();

then .. reset your new array (without the element) inside your entity..

myentity.ArrayNested = myArray;

.. then update it ...

var result = myDb.Collection.ReplaceOne(x => x.Id.Equals(myentity.Id), myentity, new UpdateOptions
            {
                IsUpsert = false
            });

Hope it helps you!!..

P.S . if you Array is not nested you can do the same but without set it in the father Entity

federico scamuzzi
  • 3,708
  • 1
  • 17
  • 24
  • unfortunatelly my.Db.Collection.Where cannot be found – OldTimeRambler Feb 06 '19 at 09:19
  • have you added using MongoDB.Driver; using MongoDB.Driver.Linq; – federico scamuzzi Feb 06 '19 at 09:21
  • From the looks of things this will download all data from that collection, edit it and then reupload it. This may be fine for small collections but is going to be awfully slow for large collections I would imagine... – Chris Feb 06 '19 at 09:36
  • but you don't have to Download ALL collection .. just the Entity you're interested . .and change the array inside it .. if your array is NOT indide ina Entity (not embedded) .. you can go directly over that – federico scamuzzi Feb 06 '19 at 10:24
  • But the array is always there. In every single document, there is this array with at least one item in it. – OldTimeRambler Feb 06 '19 at 10:34
  • It is very important to note that this method is not "thread safe" in that any updates that occur to a document _after_ it has been read but before it has been saved, will be lost when the `ReplaceOne` method is called as the completely replaces the existing document. – Pete Garafano Feb 06 '19 at 15:31
  • @PeteGarafano can you post an example with "thread safe" .. maybe possbile in LINQ style? THNX!!! .. or you mean to do a "thread safe" by lock an object or something else? – federico scamuzzi Feb 06 '19 at 16:02
  • I posted one in my answer to this question. Any time you read a document, make a client side update and save the whole document back, you run the risk of overwriting changes made by other clients. – Pete Garafano Feb 06 '19 at 16:13
  • thnx Pete ... ! – federico scamuzzi Feb 06 '19 at 16:18
0

In MongoDB you can reach that with the following script:

db.getCollection('Collection').find({},{'someArray.IsReadOnly':0})

Key point is {'someArray.IsReadOnly':0} - projection parameter for find() method.

Read about find() here - https://docs.mongodb.com/manual/reference/method/db.collection.find/index.html

venoel
  • 463
  • 3
  • 13
0

here's how to do it in a strongly typed manner using my library MongoDB.Entities. it enlists the help of ElemMatch and Unset definition builder methods.

using MongoDB.Entities;

namespace StackOverflow
{
    public class Program
    {
        public class User : Entity
        {
            public string Name { get; set; }
            public Post[] Posts { get; set; }
        }

        public class Post : Entity
        {
            public string Title { get; set; }
            public bool IsReadOnly { get; set; }
        }

        static void Main(string[] args)
        {
            new DB("test");

            var posts = new[] {
                new Post{ Title= "first post", IsReadOnly= true},
                new Post {Title = "second post", IsReadOnly = false}
            };
            posts.Save();

            (new User
            {
                Name = "Test User",
                Posts = posts
            }).Save();

            DB.Update<User>()
              .Match(f => f.ElemMatch(u => u.Posts, p => p.IsReadOnly == true || p.IsReadOnly == false))
              .Modify(d => d.Unset(u => u.Posts[-1].IsReadOnly))
              .Execute();
        }
    }
}

the following update command is sent to mongodb:

db.User.update(
    {
        "Posts": {
            "$elemMatch": {
                "$or": [
                    {
                        "IsReadOnly": true
                    },
                    {
                        "IsReadOnly": false
                    }
                ]
            }
        }
    },
    {
        "$unset": {
            "Posts.$.IsReadOnly": NumberInt("1")
        }
    }
)
Dĵ ΝιΓΞΗΛψΚ
  • 5,068
  • 3
  • 13
  • 26