How can I convert a .NET GUID to a MongoDB ObjectID (in C#). Also, can I convert it back again to the same GUID from the ObjectID?
If you're starting everything from scratch, you can type your "Id" member as a Guid
instead of ObjectId
. That's preferred because then your model doesn't have to reference MongoDB.Bson
, arguably making it a POCO class no more. You don't even need the [BsonId]
attribute if you name the member "Id", and it's better not to for the aforementioned reason.
If you're already stuck using ObjectId
in your POCO classes and--having realized the difficulties--would like to change the type of "Id" (in your class), but are not able to change the type of "_id" (in your data), you can make a custom serializer:
public class SafeObjectIdSerializer: ObjectIdSerializer
{
public SafeObjectIdSerializer() : base() { }
public override ObjectId Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
var bsonType = bsonReader.GetCurrentBsonType();
switch (bsonType)
{
case BsonType.Binary: {
var value = bsonReader
.ReadBinaryData()
.AsGuid
.ToString()
.Replace("-", "")
.Substring(0, 24);
return new ObjectId(value);
}
}
return base.Deserialize(context, args);
}
}
As MiddleTommy mentioned, going from Guid
to ObjectId
is lossy, but depending on how you use that field, that may not be a problem. The above uses the first 24 hexadecimal characters and discards the remaining 8. If you're storing random ObjectId
values and not say, an ObjectId conversion of incrementing ints, you should be fine.
If you want to also start writing ObjectId
as Guid
, it doesn't seem to hurt anything to mix "_id" types, as long as you're falling back on base.Deserialize()
, but I could be wrong and it could matter depending on your implementation. The documentation doesn't say one way or the other. To do that you can add this override to the above class:
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ObjectId value)
{
var bsonWriter = context.Writer;
var guidString = value
.ToString()
.Insert(8, "-")
.Insert(13, "-")
.Insert(18, "-")
.Insert(23, "-") + "00000000";
var asGuid = new Guid(guidString);
bsonWriter.WriteBinaryData(new BsonBinaryData(asGuid));
}
To make that your global deserializer:
public class CustomSerializationProvider : IBsonSerializationProvider
{
public IBsonSerializer GetSerializer(Type type)
{
if (type == typeof(ObjectId))
{
return new SafeObjectIdSerializer();
}
//add other custom serializer mappings here
//to fall back on the default:
return null;
}
}
Then somewhere where it will only be called once, like your Global.asax Application_Start()
BsonSerializer.RegisterSerializationProvider(new CustomSerializationProvider());