C# Driver - How do I Upsert a document and get a non-zero document ID when its an insert?

I’m trying to upsert a document into Mongo using the C# Driver with the following (simplified) method:

public MyDocumentType UpsertOne(MyDocumentType doc)
{
    var options = new FindOneAndReplaceOptions<MyDocumentType>
    {
        IsUpsert = true,
        ReturnDocument = ReturnDocument.After,
    };
    var filter = Builders<MyDocumentType>.Filter.Eq(d => d.Id, doc.Id);
    var upsertedDoc = _collection.FindOneAndReplace(filter, doc, options);
    return doc;
}

Neither the upsertedDoc nor doc objects have a nonzero objectID.

However, When I insert the doc with InsertOne() Mongo generates a docID as expected:

public MyDocumentType InsertOne(MyDocumentType doc)
{
    _collection.InsertOne(doc);
    return doc;
}

The ObjectId field in my document class is nonnullable, and initializes to zeros, but on InsertOne it gets proper documentID but when using FindOneAndReplace with the IsUpsert=true option it simply inserts a doc with a zero’d Id.

Here is how I’m calling the various methods, and the output:

var db = new MongoService();
db.Connect();
var documentWithZeroedId = db.UpsertOne(document);
Console.WriteLine($"Upsert Document ID: {documentWithZeroedId.Id}");

var documentWithValidId = db.InsertOne(document);
Console.WriteLine($"Insert Document ID: {documentWithValidId.Id}");

// Output:
Connected to Mongo!
Upsert Document ID: 000000000000000000000000
Insert Document ID: 6620b59ca4421c3c294c0b09

What is the proper means to upsert a doc and when inserting, get a valid non-zeroed doc ID?

The complete console app demonstrating this issue is:

using MongoDB.Driver;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Security.Cryptography.X509Certificates;
using System.Xml.Xsl;

public class DictionaryValue
{
    public int Id;
    public string Value;
}

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class BsonCollectionAttribute : Attribute
{
    public string CollectionName { get; }

    public BsonCollectionAttribute(string collectionName)
    {
        CollectionName = collectionName;
    }
}

[BsonCollection("Documents")]
public class MyDocumentType 
{
    [BsonId]
    [BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]
    public ObjectId Id;
    public DateTime CreatedAt => Id.CreationTime;

    public string TopLevelField;
    public Dictionary<string, DictionaryValue> Values;

}
class MongoService { 
    private MongoClient _client;
    private IMongoDatabase _db;
    private IMongoCollection<MyDocumentType> _collection;

    public void Connect()
    {
        try
        {
            var settings = MongoClientSettings.FromConnectionString("mongodb://localhost:27018");
            _client = new MongoClient(settings);
            _db = _client.GetDatabase("dictionaryTest");
            Console.WriteLine("Connected to Mongo!");
            _collection = _db.GetCollection<MyDocumentType>("Documents");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error on connect to Mango database: {error}", ex);
            throw;
        }
    }

    public MyDocumentType UpsertOne(MyDocumentType doc)
    {
        var options = new FindOneAndReplaceOptions<MyDocumentType>
        {
            IsUpsert = true,
            ReturnDocument = ReturnDocument.After,
        };

        var filter = Builders<MyDocumentType>.Filter.Eq(d => d.Id, doc.Id);
        var upsertedDoc = _collection.FindOneAndReplace(filter, doc, options);
        return doc;
    }

    public MyDocumentType UpsertOneSlow(MyDocumentType doc)
    {
        var existing = _collection.Find(d => d.Id == doc.Id).FirstOrDefault();
        if (existing != null)
        {
            _collection.ReplaceOne(d => d.Id == doc.Id, doc);
            return doc;
        } else
        {
            _collection.InsertOne(doc);
            return doc;
        }
    }

    public MyDocumentType InsertOne(MyDocumentType doc)
    {
        _collection.InsertOne(doc);
        return doc;
    }
}

class Program
{
    static int Main(String[] args)
    {
        var document = new MyDocumentType();
        document.TopLevelField = "Dictionary of Integers";
        document.Values = new Dictionary<string, DictionaryValue>()
        {
            { "1", new DictionaryValue {Id = 1, Value = "1"} },
            { "2", new DictionaryValue {Id = 2, Value = "1"} },
            { "3", new DictionaryValue {Id = 3, Value = "2"} },
        };

        var db = new MongoService();
        db.Connect();

        var documentWithZeroedId = db.UpsertOne(document);
        Console.WriteLine($"Upsert Document ID: {documentWithZeroedId.Id}");

        var documentWithValidId = db.InsertOne(document);
        Console.WriteLine($"Insert Document ID: {documentWithValidId.Id}");

        return 0;
    }
}

Hi Tim,

Thanks for posting this. The behavior outlined does not seem to be expected and you should be getting a valid object ID back. I’ve created a ticket for our team to validate this which you can follow for updates.

Thanks,

Rishit.

Thanks Rishit.

Note also my question posted on SO

Is there an update on this? I’m having the same problem.