CSMongo Driver - Part 1
Lately, I've been spending a lot of my time writing a driver for Mongo in C#. The primary goal was to make it comfortable to work with dynamic objects in a static language.
I still have a lot of code to write but I'm getting close to a working version that I can get out the door. This post discusses how my Mongo driver works currently works.
Dynamic In Static-Land
The next version of .NET introduces the dynamic keyword but until then I needed to build a class that worked with C# and was dynamic enough to work with Mongo. The main hurdle is that a 'dynamic' isn't just a dictionary of key/value pairs. Instead, an object can be nested several level deep.
Additionally, data types between static languages and dynamic languages are also handled differently. Think about numbers for a second. In C# you have single, double, float, decimal, etc. - But Javascript? Yeah, you have Number -- A heck of a lot easier to deal with but can cause problems when trying to pull a value back out of your object.
Below are some samples how you can create Mongo document with this driver.
//Creating And Assigning Vaues //=============================== MongoDocument document = new MongoDocument(new { name = "Hugo", age = 29, admin = true }); //make changes and assign new values using += document += new { age = 30, settings = new { color = "red", width = 700 }, permissions = new string[] { "read", "write", "delete" } }; //make changes using the path to a field document["name"] = "Hugoware"; document["settings.color"] = "blue"; //or create new objects entirely document["settings.urls.favorite"] = "http://hugoware.net"; //remove fields using -= and a string or string array document -= "name"; document -= "settings.color"; //or remove multiple items document -= new string[] { "age", "permissions" }; //also use typical methods to perform same changes document.Set("name", new { first = "Hugo", last = "Bonacci" }); document.Remove("settings", "name", "age"); //Merging multiple documents //=============================== MongoDocument first = new MongoDocument(); MongoDocument second = new MongoDocument(new { name = "Hugo" }); //merge either of the two ways listed first += second; /* or */ first.Merge(second); string name = first["name"]; // "Hugo"
This class allows for a lot of flexability to write to an object and make changes to it without needing to know about the object.
Going back to data types, each object has a value handler that manages working with the type and performing conversions. You are also able to provide a default value in case the property requested doesn't exist or can't be converted.
Here are a few more examples of accessing values types.
//Accessing Typical Values //=============================== //create an empty object and access not real values MongoDocument document = new MongoDocument(); //accessing by default property returns an object //NOTE: Using Get() is better for accessing values int a = (int)document["not.real"]; // *crash* null int? b = document["not.real"] as int?; // null //accessing by Get<T>() returns default(T) int c = document.Get<int>("not.real"); // 0 //or define a fallback value int d = document.Get<int>("not.real", 55); // 55 //next examples are assigned '54.342' which defaults to 'double' document.Set("value", 54.342); //access by default property double e = (double)document["value"]; // 54.342 //or use the Get method that allows a cast request type double f = document.Get<double>("value"); // 54.342 int g = document.Get<int>("value"); // 54 bool h = document.Get<bool>("value"); // true string i = document.Get<string>("value"); // "54.342" //Accessing Uncommon Values //=============================== doc["count"] = long.MaxValue; int a = (int)doc["count"]; // *crash* int b = doc.Get<int>("count"); // 0 long c = doc.Get<int>("count"); // 0 long d = doc.Get<long>("count"); // 9223372036854775807 doc["count"] = uint.MaxValue; uint e = doc.Get<uint>("count"); // 4294967295 long f = doc.Get<long>("count"); // 4294967295 decimal h = doc.Get<decimal>("count"); // 4294967295 int g = doc.Get<int>("count", int.MaxValue); // 0 (no default!)
As you can see there are a lot of different ways data types like this could play out. Failed conversions tend to be easier to detect than incorrect conversions. I suspect this will improve over time.
If you don't like the way conversions are handled for a certain type, or if you have a custom class you want special rules created for, can define your own custom DataTypes that handle the formatting, parsing and saving to the database.
Database Queries
If you've grown accustomed to SQL queries then you're going to be in a world of hurt with Mongo. They make sense after a bit of time but not like the somewhat human-readable format of a standard SQL query.
In this driver I've attempted to to make a LINQ-ish style query builder. This builder is used in a variety of places depending on the purpose (such as selection as opposed to updating).
Here are some query examples.
MongoDatabase database = new MongoDatabase("100.0.0.1", "website"); //simple chained query database.From("users") .Less("age", 50) .EqualTo("group", "admins") .Select(); //anonymous types for parameters database.From("users") .In("permissions", "write", "delete") .Select(new { take = 30 }); //updating without a record using a //query selector and update arguments database.From("users") .Match("name", "^(a|b|c)", RegexOptions.IgnoreCase) .Update(new { enabled = false, status = "Disabled by Admin" });
Mongo queries appear to be missing several pretty important features that might make it a little more difficult for you to find records. I figured out how to use jLinq directly within Mongo so I'm going to see if version two might include a little jLinq support.
I'm hoping I'll have the first version of my driver released sometime this week so check back for more news on the progress!
February 21, 2010
CSMongo Driver - Part 1
Post titled "CSMongo Driver - Part 1"