Reputation: 1094
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;
}
}
Upvotes: 1
Views: 277
Reputation: 22456
In your sample, you first generate a new document with an empty ObjectId
. This id is initialized with all zeros. When inserting the document, the driver recognizes the empty ObjectId
and lets MongoDB assign a new id value.
When upserting, however, you explicitely look for a document with an id with a zero value (valid, but very uncommon). The driver handles the situation differently and inserts the document with the all zero ObjectId.
Instead of relying on the database server to assign the id, I have found it is best to assign the ids in C# code in order to avoid situations where you sometimes end up with an unassigned id. You could change your code like this to assign the id directly in the code:
public class MyDocumentType
{
public ObjectId Id { get; set; } = ObjectId.GenerateNewId();
public DateTime CreatedAt => Id.CreationTime;
public string TopLevelField;
public Dictionary<string, DictionaryValue> Values;
}
The property initializer asserts that the id is set directly when creating an instance; of course, you can assign another one later on.
Please note that after the change, the sample will raise an error for the InsertOne
operation as the code tries to insert another document with the same id.
Upvotes: 0