C# mongodb driver 2.0 - How to upsert in a bulk operation?
Asked Answered
E

3

29

I migrated from 1.9 to 2.2 and reading the documentation I was surprised to discover that is not possible to upsert during a bulk operation anymore, since operations don't allow options.

bulkOps.Add(new UpdateOneModel<BsonDocument>(filter, update));
collection.BulkWrite(bulkOps);

Should be

options.isUpsert = true;
bulkOps.Add(new UpdateOneModel<BsonDocument>(filter, update, options));
collection.BulkWrite(bulkOps);

Is this work in progress, intended, or I'm missing something? Thank you.

Emu answered 28/2, 2016 at 19:53 Comment(0)
D
37

Set the IsUpsert property of the UpdateOneModel to true to turn the update into an upsert.

var bulkOps = new List<WriteModel<BsonDocument>>();
// Create and add one or more write models to list
var upsertOne = new UpdateOneModel<BsonDocument>(filter, update) { IsUpsert = true };
bulkOps.Add(upsertOne);
// Write all changes as a batch
collection.BulkWrite(bulkOps);
Deciliter answered 28/2, 2016 at 21:39 Comment(5)
This should be added to documentation. Thank you!!Emu
what is bulkOps? How do I get one?Cockroach
@gyozokudor bulkOps is List<WriteModel<T>>Raindrop
What should I put in filterand update variables?Vesuvius
When your Id is a string, this does not work.Driver
T
36

given mongo collection

IMongoCollection<T> collection

and enumerable of records to insert where T has Id field.

IEnumerable<T> records 

this snippet will do a bulk upsert (the filter condition may be changed according to the situation):

var bulkOps = new List<WriteModel<T>>();
foreach (var record in records)
{
    var upsertOne = new ReplaceOneModel<T>(
        Builders<T>.Filter.Where(x => x.Id == record.Id),
        record)
    { IsUpsert = true };
    bulkOps.Add(upsertOne);
}
collection.BulkWrite(bulkOps);
Theobald answered 21/3, 2018 at 13:52 Comment(2)
I like this approach but is it true that each "record" in "records" must contain an _Id value? The source of my records does not include the _Id field. So I need to locate single records using an alternate unique key, e.g. ukey. When I used x.ukey == record.ukey the update failed because the record._Id field is all zeros. Is there a good way to do this in one upsert or do I have to fetch the target records so I can set the _Id column before doing the upsert?Afb
I hesitated to use after i saw Replace. So used UpdateOneModel it was too slow. Just came again and checked it booom ! It's so fast.Beguile
H
1

Here is an extension method based on @Aviko response

public static BulkWriteResult<T> BulkUpsert<T>(this IMongoCollection<T> collection, IEnumerable<T> records)
    {
        string keyname = "_id";

        #region Get Primary Key Name 
        PropertyInfo[] props = typeof(T).GetProperties();

        foreach (PropertyInfo prop in props)
        {
            object[] attrs = prop.GetCustomAttributes(true);
            foreach (object attr in attrs)
            {
                BsonIdAttribute authAttr = attr as BsonIdAttribute;
                if (authAttr != null)
                {
                    keyname = prop.Name;
                }
            }
        }
        #endregion

        var bulkOps = new List<WriteModel<T>>();


        foreach (var entry in records)
        {
            var filter = Builders<T>.Filter.Eq(keyname, entry.GetType().GetProperty(keyname).GetValue(entry, null));

            var upsertOne = new ReplaceOneModel<T>(filter, entry){ IsUpsert = true };

            bulkOps.Add(upsertOne);
        }

        return collection.BulkWrite(bulkOps);

    }
Hydrolysis answered 7/1, 2020 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.