If you’ve played with MongoDB you’ve discovered that communicating with MongoDb via Documents is painful. It is painful because we don’t care about Documents. We care about storing our domain objects and having powerful ways of fetching them again from the data store. How do we accomplish those tasks when then tool doesn’t offer those options?

The answer is, of course, we extend the tool so that we can express our intent in the most natural language we can. To me this means Linq or some reasonable subset thereof.
So lets start with the domain object Person:
[Serializable]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
The object is marked Serializable but is otherwise unremarkable. If I have an instance of person I’d like to be able to simply pass it to a Save method on the collection as follows.
var person = new Person
{
FirstName = "Loreena",
LastName = "McKennitt"
};
_people.Save(person);
We can’t change the IMongoCollection object directly but we can use an extension method to build the interface we’re looking for:
public static class IMongoCollectionExtensions
{
public static void Save<T>(this IMongoCollection collection, T item) where T: class
{
// magic happens
}
}
Now to drive out the magic part. Fundamentally the data in the object we want to save has to be translated to something MongoDB understands – a Document. We’ll take this in stages by translating first to a Dictionary then to a Document. Here are some tests to drive out the functionality of a ToDictionary() extension.
public class TExtensionsTests
{
[TestFixture]
public class When_asked_to_convert_a_class_to_a_Dictionary
{
private const string FirstNameKey = "FirstName";
private const string FirstNameValue = "Loreena";
private const string LastNameKey = "LastName";
private const string LastNameValue = "McKennitt";
private Person _person;
[SetUp]
public void BeforeEachTest()
{
_person = new Person
{
FirstName = FirstNameValue,
LastName = LastNameValue
};
}
[Test]
public void Should_associate_property_values_with_their_names()
{
var result = _person.ToDictionary();
result[FirstNameKey].ShouldBeEqualTo(FirstNameValue);
result[LastNameKey].ShouldBeEqualTo(LastNameValue);
}
[Test]
public void Should_store_property_names_as_keys()
{
var result = _person.ToDictionary();
result.ContainsKey(FirstNameKey).ShouldBeTrue();
result.ContainsKey(LastNameKey).ShouldBeTrue();
}
}
}
We could implement this with some reflection by recursively iterating over the Properties and Fields in the Person and transferring their values to a Dictionary but for now we’ll be lazy and take advantage of another tool to do the heavy lifting for us for now – Json.NET. Go ahead and download a copy to your library and add a reference to your project.
public static class TExtensions
{
public static IDictionary<string,object> ToDictionary<T>(this T item) where T: class
{
string json = JsonConvert.SerializeObject(item, Formatting.None);
var dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
return dictionary;
}
}
Next we’ll move the Dictionary contents into the Document. Since Document doesn’t expose anything useful that can consume a Dictionary or IDictionary, we have to add the key-value pairs one-by-one. First a convenience extension for performing an action on all items in an IEnumerable:
public static class IEnumerableExtensions
{
public static IEnumerable<T> DoToEach<T>(this IEnumerable<T> items, Action<T> action)
{
foreach(var item in items)
{
action(item);
}
return items;
}
}
Next more tests to drive out the implementation of the ToDocument() extension:
public class IEnumerableKeyValuePairOfStringToObjectExtensionsTests
{
[TestFixture]
public class When_asked_to_convert_to_a_Document
{
private const string FirstNameKey = "FirstName";
private const string FirstNameValue = "Loreena";
private const string LastNameKey = "LastName";
private const string LastNameValue = "McKennitt";
private Dictionary<string, object> _dictionary;
[SetUp]
public void BeforeEachTest()
{
_dictionary = new Dictionary<string, object>
{
{ FirstNameKey, FirstNameValue },
{ LastNameKey, LastNameValue }
};
}
[Test]
public void Should_associate_key_values_with_their_names()
{
var result = _dictionary.ToDocument();
result[FirstNameKey].ShouldBeEqualTo(FirstNameValue);
result[LastNameKey].ShouldBeEqualTo(LastNameValue);
}
[Test]
public void Should_copy_all_keys_to_the_Document()
{
var result = _dictionary.ToDictionary();
result.ContainsKey(FirstNameKey).ShouldBeTrue();
result.ContainsKey(LastNameKey).ShouldBeTrue();
}
}
}
Then the implementation:
public static class IEnumerableKeyValuePairOfStringToObjectExtensions
{
public static Document ToDocument<T>(this T items) where T : IEnumerable<KeyValuePair<string,object>>
{
var document = new Document();
items.DoToEach(x => document.Add(x.Key, x.Value));
return document;
}
}
And now the magic part of the Save method:
public static class IMongoCollectionExtensions
{
public static string Save<T>(this IMongoCollection collection, T item) where T : class
{
var dictionary = item.ToDictionary();
var document = dictionary.ToDocument();
collection.Update(document);
return document["_id"].ToString();
}
}
Now if we call the Save method it will be persisted in the collection but how will we know it worked? Well, we should write some tests around the Save() method to verify that _people.Update() is called and populated correctly. I’ll leave that as an exercise for the reader for now. One thing we can do is write an integration test to check the return value of the Save() method.
[TestFixture]
public class When_trying_basic_tasks_fluently
{
private IMongoCollection _people;
[SetUp]
public void BeforeEachTest()
{
var mongo = new Mongo();
mongo.Connect();
var db = mongo.getDB("tests");
_people = db.GetCollection("people");
}
[Test]
public void Should_be_able_to_store_an_object()
{
var person = new Person
{
FirstName = "Loreena",
LastName = "McKennitt"
};
var dbId = _people.Save(person);
dbId.ShouldNotBeNull();
}
}
That’s enough for now. In Part 2 of this post we’ll delve into how to reverse this process to get a Person back from the collection instead of a Document.
Interesting article, thanks for this. Will you be covering more
tutorials on indexing, many to many relationships?
Just to get us RDBMS developers up to speed:)
Cheers
Benn
My first goal is to reimplement the simple quickstart capabilities (Insert, Find, Delete) on T, after that who knows.