16

I am using MongoDB 2, and I want to update multiple documents and upsert a value like processed:true into the collection. But MongoDB c# api only allows us to either Update Multiple Records or Upsert a single record.

How to solve this problem using the C# api?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Nitin Agarwal
  • 563
  • 1
  • 6
  • 19
  • Possible duplicate of [C# mongodb driver 2.0 - How to upsert in a bulk operation?](https://stackoverflow.com/questions/35687470/c-sharp-mongodb-driver-2-0-how-to-upsert-in-a-bulk-operation) – Mugen Sep 24 '19 at 12:43

7 Answers7

20

After Mongo 2.6 you can do Bulk Updates/Upserts. Example below does bulk update using c# driver.

MongoCollection<foo> collection = database.GetCollection<foo>(collectionName);
      var bulk = collection.InitializeUnorderedBulkOperation();
      foreach (FooDoc fooDoc in fooDocsList)
      {
        var update = new UpdateDocument { {fooDoc.ToBsonDocument() } };
        bulk.Find(Query.EQ("_id", fooDoc.Id)).Upsert().UpdateOne(update);
      }
      BulkWriteResult bwr =  bulk.Execute();
PUG
  • 4,301
  • 13
  • 73
  • 115
  • 4
    Our Development team uses -MongoDB 3.0.0 and -MongoDB C# Driver Version 1.7.0.4714. The MongoDB C# Driver Version 1.7.0.4714 complains about InitializeUnorderedBulkOperation() being undefined. – CS Lewis Dec 12 '15 at 08:02
  • 1
    while this is the accepted answer, its outdated. see https://stackoverflow.com/questions/35687470/c-sharp-mongodb-driver-2-0-how-to-upsert-in-a-bulk-operation/35688613#35688613 – Mugen Sep 24 '19 at 12:43
  • The comment above is incorrect and is for upserting documents -- which is not the same a updating individual fields in multiple documents. – Anthony Nichols Apr 19 '22 at 14:44
16

You cannot do it in one statement.

You have two options

1) loop over all the objects and do upserts

2) figure out which objects have to get updated and which have to be inserted then do a batch insert and a multi update

jeffsaracco
  • 1,259
  • 12
  • 21
10

For those using version 2.0 of the MongoDB.Driver, you can make use of the BulkWriteAsync method.

<!-- language: c# -->
// our example list
List<Products> products = GetProductsFromSomewhere();  

var collection = YourDatabase.GetCollection<BsonDocument>("products"); 

// initialise write model to hold list of our upsert tasks
var models = new WriteModel<BsonDocument>[products.Count];

// use ReplaceOneModel with property IsUpsert set to true to upsert whole documents
for (var i = 0; i < products.Count; i++){
    var bsonDoc = products[i].ToBsonDocument();
    models[i] = new ReplaceOneModel<BsonDocument>(new BsonDocument("aw_product_id", products[i].aw_product_id), bsonDoc) { IsUpsert = true };
};

await collection.BulkWriteAsync(models); 
Joey
  • 771
  • 6
  • 7
  • 9
    That helped me. Thanks a lot. In my case I get an IEnumerable as items to be upserted. `BulkWriteAsync` takes an IEnumerable, so I modified it to something like this: `var models = items.Select(item => new ReplaceOneModel(new ExpressionFilterDefinition(doc => doc.Id == item.Id), item) { IsUpsert = true });` – F. Fix Sep 24 '15 at 08:22
2

Try first removing all items to be inserted from the collection, and then calling insert:

        var search = [];
        arrayToInsert.forEach(function(v, k) {
            search.push(v.hash); // my unique key is hash. you could use _id or whatever
        })
        collection.remove({
            'hash' : {
                $in : search
            }
        }, function(e, docs) {

            collection.insert(arrayToInsert, function(e, docs) {
                if (e) {
                    console.log("data failed to update ", e);
                }
                else {
                    console.log("data updated ");
                }
            });
        })
Henry
  • 2,870
  • 1
  • 25
  • 17
  • 2
    The one problem with this is that clients reading the collection may see missing objects during the operation. If your clients can tolerate that, then this seems a reasonable approach. – Tony K. Jun 05 '14 at 14:22
1

UpdateFlags is an enum in the C# driver that will let you specify both at once. Just like any other flags enum, you do this by bit "or"ing.

var flags = UpdateFlags.Upsert | UpdateFlags.Multi;

You can read the docs on enums here (http://msdn.microsoft.com/en-us/library/cc138362.aspx) paying special attention to the section on Enumeration Types as Bit Flags

Craig Wilson
  • 12,174
  • 3
  • 41
  • 45
1

Working with mongoose@5.9.9 - try initializeUnorderedBulkOp():

export const InfoFunc = (Infos: Infos[]) => {

  const bulk = InfoResult.collection.initializeUnorderedBulkOp();
  Infos.forEach((info: Infos) => bulk.find({ "Id": info.Id}).upsert().updateOne(info));
  bulk.execute();
}
Jason Mullings
  • 850
  • 14
  • 10
1

A prettier and more robust approach:

public interface IProjection
{
    Guid Id { get; }
    bool IsDeleted { get; }
}
public async Task UpsertManyAsync<TProjection>(IEnumerable<TProjection> replacements, CancellationToken cancellationToken)
    where TProjection : IProjection
{
    var requests = replacements.Select(replacement => new ReplaceOneModel<TProjection>(
        filter: new ExpressionFilterDefinition<TProjection>(projection => projection.Id == replacement.Id),
        replacement: replacement) { IsUpsert = true });

    await _context
        .GetCollection<TProjection>()
        .BulkWriteAsync(
            requests: requests,
            options: new BulkWriteOptions { IsOrdered = false },
            cancellationToken: cancellationToken);
}
  • really cool. where did you come up with this? just to help others: this is for upsert single data item: await collection.ReplaceOneAsync(x => x.Id == data.Id, data, new ReplaceOptions { IsUpsert = true }).ConfigureAwait(false); – yBother Aug 31 '22 at 09:57