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