Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I find a document by GUID _id?

Tags:

c#

mongodb

I use this code to retrieve some documents:

var client = new MongoClient(connectionString);
var database = client.GetDatabase(databaseName);
var collection = database.GetCollection<BsonDocument>(collectionName);

var json = "{created: {$gte: ISODate(\"2018-12-20T00:00:00.000Z\"), $lt: 
ISODate(\"2018-12-21T00:00:00.000Z\")}}";
BsonDocument query = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(json);
var documents = collection.Find(query).Limit(10);

The results look like this:

{ "_id" : CSUUID("75c5634c-b64b-4484-81f5-5b213228e272"), ..., "created" : ISODate("2018-12-20T23:59:13.375Z") }

I'm having trouble retrieving this same document when trying to filter on _id. Here are the filters I've tried (using the same code as above) and have not been able to retrieve the document:

var json = "{ \"_id\" : \"75c5634c-b64b-4484-81f5-5b213228e272\" }";
var json = "{ \"_id\" : CSUUID(\"75c5634c-b64b-4484-81f5-5b213228e272\") }";
var json = "{ \"_id\" : new BinData(4, \"TGPFdUu2hESB9VshMijicg==\") }";
var json = "{ \"_id\" : BinData(4, \"TGPFdUu2hESB9VshMijicg==\") }";
var json = "{ \"_id\" : new BinData(3, \"TGPFdUu2hESB9VshMijicg==\") }";
var json = "{ \"_id\" : BinData(3, \"TGPFdUu2hESB9VshMijicg==\") }";
var json = "{ \"_id\" : { $eq: \"TGPFdUu2hESB9VshMijicg==\" } }";
var json = "{ \"_id\" : { $binary: \"TGPFdUu2hESB9VshMijicg==\", $type: 4 } }";
var json = "{ \"_id\" : { $binary: \"TGPFdUu2hESB9VshMijicg==\", $type: 3 } }";

Note, TGPFdUu2hESB9VshMijicg== was retrieved by getting a base 64 encoded string from the guid like this:

Convert.ToBase64String((new Guid("75c5634c-b64b-4484-81f5-5b213228e272")).ToByteArray())

None of the queries throw any exceptions, but they return no documents.

like image 768
Eilert Hjelmeseth Avatar asked Dec 26 '18 12:12

Eilert Hjelmeseth


2 Answers

The MongoDb C# driver does a lot of work trying to keep you away of the Json (Bson) representation. To oversimplify, you have 3 ways of working with MongoDb with C#

  • use raw Json (which is very "server-side" progamming).
  • use the Swiss army knife BsonDocument class.
  • use typed C# classes.

And of course, a combination of the 3, which makes things much worse :-)

So, in your case, here is how you would do the BsonDocument way (w/o any JSON):

var client = new MongoClient(myConnectionString);
var db = client.GetDatabase("myDb");
var guid = Guid.NewGuid();

// create an untyped document
var doc = new BsonDocument { { "_id", guid } };
var coll = db.GetCollection<BsonDocument>("myColl");
coll.InsertOne(doc);

// Builders<T> is central to help you build all sorts of mongodb JSON jargon (filters, sort, projections, etc.)
// instead of building it by yourself
var filter = Builders<BsonDocument>.Filter.Eq(new StringFieldDefinition<BsonDocument, Guid>("_id"), guid);
var foundDoc = coll.Find(filter).FirstOrDefault();
Console.WriteLine(foundDoc["_id"]);

And here is how you could do the typed-document way (w/o any JSON and w/o any BsonDocument):

var client = new MongoClient(myConnectionString);
var db = client.GetDatabase("myDb");
var guid = Guid.NewGuid();

// create a class
var doc = new MyDoc { Id = guid };
var coll = db.GetCollection<MyDoc>("myColl");
coll.InsertOne(doc);

// we use a type that correspond to our busines layer/logic
// that's the easier way because you can use Linq syntax so we're far from JSON and document issues
// plus it's super readable in C#
var foundDoc = coll.Find(d => d.Id == guid).FirstOrDefault();
Console.WriteLine(foundDoc.Id);
...

// the typed-document (class)
class MyDoc
{
    [BsonId]
    public Guid Id { get; set; }

    ... other properties...
}

As you see, the last way is much simpler, but we can't always use it. BTW, it's sad that the driver doesn't allow to derive MyDoc from BsonDocument, because we would truly have best of both worlds (it compiles but throws.... if MongoDb C# devs read this...)

Now, concerning guids, you'll note the Console.WriteLine(foundDoc["_id"]) displays UuidLegacy:0x87fa981983de774b998868046e257b19 because MongoDb has a legacy history with guids.

As you found out, you can change BsonDefaults.GuidRepresentation. By default it's CSharpLegacy.

Here is a list of prefixes used when guids are displayed as strings throughout code (client or server):

  • UUID: the standard one (not legacy)
  • LUUID: the legacy one (only seen on server)
  • CSUUID: the C# legacy one (only seen on clients)
  • JUUID: the Java legacy one (only seen on clients)
  • PYUUID : the Python legacy one (only seen on clients)

The 2 and 3 approaches also shield you from these "internal" MongoDb issues. If you use these, you don't have to change BsonDefaults.GuidRepresentation.

So my advise is try to stay away from Json when programming MongoDb with C#.

like image 64
Simon Mourier Avatar answered Oct 13 '22 10:10

Simon Mourier


Adding this before even creating MongoClient() resolved the issue for me:

BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;

It looks like on the C# side the MongoDB driver was interpreting it as a UUID with a binary subtype of 3. However, the documents saved in the collection had a binary subtype of 4.

Also, after this change the document retrieved shows "UUID()" rather than "CSUUID()":

{ "_id" : UUID("75c5634c-b64b-4484-81f5-5b213228e272"), ..., "created" : ISODate("2018-12-20T23:59:13.375Z") }

After more time than I'd care to admit searching the web and testing many theories, the breakthrough came from skimming through this article: https://www.codeproject.com/Articles/987203/%2FArticles%2F987203%2FBest-Practices-for-GUID-data-in-MongoDB

Excerpt from that link:

MongoDB drivers usually store UUIDs as Binary fields with the legacy 0x03 subtype assigned by default. This configuration can be changed:

C#:

You can override the driver’s default settings and configure it to use the Binary 0x04 subtype by modifying the value of BsonDefaults.GuidRepresentation:

BsonDefaults.GuidRepresentation = GuidRepresentation.Standard; 

You can also modify GuidRepresentation at the server, database and collection level.

EDIT:

This is what I ended up using for the json filter:

var json = "{ \"_id\" : UUID(\"75c5634c-b64b-4484-81f5-5b213228e272\") }";
like image 40
Eilert Hjelmeseth Avatar answered Oct 13 '22 10:10

Eilert Hjelmeseth