Hugoware

The product of a web developer with a little too much caffeine

CSMongo Driver – Part 1

with 8 comments

My new project CSMongo, a Mongo driver for .NET, is now online! Check it out!

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 — which is a project I’ve worked on before in the past.

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 supportHow cool would that be?

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!

Written by hugoware

February 21, 2010 at 10:31 pm

8 Responses

Subscribe to comments with RSS.

  1. Wow, I’m really eager to see what you’ve come up with. I like the fact the attributes, while dynamic, are bound back to a type. That eliminates lots of errors versus when you keep attributes as strings, have to convert, etc.

    Sounds like you’re on fire with this! Keep it up.

    ActiveEngine Sensei

    February 22, 2010 at 7:40 am

    • Thanks – Right now only the Get method attempts type conversion. Each of the data types has an “Acceptable Type” validation for the Set functionality – If there is no acceptable conversion then it replaces the type with an appropriate data container.

      So for example, if you had the MongoNumber (a double) and did .Get<int>("val") it would return an int value but leave the container intact – but if you did a .Set("val", int.MaxValue) it would replace the type to a MongoInt32.

      Even though a valid conversion exists between double and int, it favors replacement over conversion. Not sure if that is the best way to handle it though.

      I went that route because if I tried to convert things by default then you might end up with situations like .Set("val", "400") — Should I parse the string into an int value? Or replace the field with a string? I opted for the latter but I’ll have to see what other people think as the project moves forward…

      Anyways… rambling again… I’ll stop now 🙂

      hugoware

      February 22, 2010 at 11:52 am

  2. […] Code doesn’t appear to be released yet but you can follow his progress at his blog, Hugoware. […]

  3. Very slick syntax. I’ve written some code that does some of these data-type ‘assumption conversions’ before, and I have to say the way you implemented this is bloody elegant.

    Look forward to seeing what you come up with.

    Mike

    February 25, 2010 at 8:17 am

    • Cool – Thanks! I’m hoping I have some public code Monday this next week (at least the ‘essential’ and ‘recommended’ features as per the website)

      hugoware

      February 25, 2010 at 11:20 am

  4. Hi Hugo,

    The EqualsTo doesn’t seem to work. Im using the latest update from Git.

    Every time I perform EqualTo with value type, it doesn’t work. If EqualsTo with string, I got an exception: Parameter Count Mismatch.

    Am I missing something here? Looking at the codebase, seems the __Populate method of BsonObject causing this, while it ignores value type, it throws exception for string type when trying to get value of char[]

    Please advice.

    PS: just out of curiosity, is there any unittest against csmongo? do you accept any contributors to csmongo?

    Thanks heaps..

    Hery

    March 23, 2010 at 3:12 pm

    • Hmm… I’ll check it out and see what I can find. If I had to guess most likely it is trying to merge a document with a string…

      As far as tests, I have a small number of tests I’ve written but not using a real framework – Just something I hacked together so I could keep working.

      If you’re interesting in working on CSMongo then that would be cool – Send me your e-mail address using the contact form on my site and we can discuss it further. http://hugoware.net/contact

      Hugo

      hugoware

      March 23, 2010 at 3:28 pm

  5. Great work on this, very interesting.

    I’m having some issues when querying based on nested data. Say I have some documents that looks something like this:

    {
    myid:1
    name:”some name”
    metadata: {
    metakey1: “some value”
    metakey2: “some other value”
    }

    To return all documents with a metakey1 value equal to “some value” I could do:

    db.test.find({“metadata.metakey1″:”some value”})

    But when I try to achieve this using CSMongo I do not get any documents returned. It seems the query being executed is actually:

    db.test.find({metadata: {“metakey1″:”some value”}})

    This does not return any documents because when querying with embedded documents the parameters passed have to match the whole document.

    Is there anyway I can get CSMongo to emit a query using the dot notation instead of embedded documents?

    Thanks,
    Adrian.

    Adrian

    September 26, 2010 at 11:16 am


Leave a reply to hugoware Cancel reply