Project musings

Fluent MongoDB Part 3

In Parts 1 and 2 of this series we built a simple fluent interface that allowed us to save, fetch, and delete domain objects in a MongoDB data store.

At this point you’re probably wondering whether you really have to build out all the fluent tooling yourself just to be able easily store and fetch your domain objects. Well, it depends on the API you want to use. You could roll it yourself as we’ve been doing or you could go with an existing library. It turns out mongodb-csharp does come with a fluent query interface called MongoDB.Linq that provides the ability to build queries from expressions. As we’ve seen, however, mongodb-csharp does not currently provide strongly typed collections so we’d still end up going with a hybrid implementation requiring lots of non-business support code.

Alternatively, we could try a different C# adapter for MongoDB – one that provides both strongly typed collections that can use our domain objects, and the ability to query the store using Linq. An adapter that meets those requirements is NoRM and in this Part we’ll be rebuilding our test app using that tool. Let’s go.

You’ll notice a couple of additions to the version we had in Part 1. The first is the Id property and second is the MongoIgnoreIfNull attribute on all the nullable properties. If you’ve gone much beyond where we were in Part 2 you’ll have realized that you really need a property to hold the MongoDB-id for the object in order to perform efficient queries. We have the same constraint in NoRM.

The other problem you may have encountered while trying mongodb-csharp is explicitly querying on a null property. The NoRM solution to that problem is to decorate the appropriate properties with a hint as shown above.

Now to drive out some functionality. To start with, we’ll build an instance of the Person:

NoRM takes care of translating our object to something MongoDB understands, so all we have to do is call Save():

_people.Save(person);

Last we need to verify that the object ends up with a MongoDB-id:

person.Id.ShouldNotBeNull();

Super simple. Next let’s drive out the query implementation. Once again NoRM provides the FirstOrDefault() funcionality so we just have to get a queryable collection and call it. If you want to use Linq in NoRM you have to get an instance of MongoQuery. Note: This means we have to use two different objects to interact with the database, one for saving and another for queries.

MongoQuery wants a MongoQueryProvider to query against and that object wants the name of the database, not the name of the collection. This has implications on table naming so it might be best not to give an explicit name when getting a typed collection.

For now we’ll keep it simple and create whichever type we need on the fly:

The MongoIgnoreIfNull attribute comes into play when we call FirstOrDefault or any other Linq query method. If we hadn’t used that attribute the query would have been translated to something equivalent to:

select top 1
from Person
where FirstName = "Loreena"
and LastName is null
and Id is null

Not what I wanted at all… but now we have a different problem. Since the inclusion of null in the query is determined by an attribute, we can’t change it on the fly. Perhaps I really did want the person who didn’t have a last name. Just something to think about.

Let’s move on. NoRM provides two methods for deleting. There’s Delete(T object) that wants an object that already exists in the data store – it is going to use the Id property.

_server.GetCollection<Person>().Delete(person);

And there’s Delete<T>(T object) that wants a template for the kinds of things you want to delete.

This is place where you might accidentally call the wrong one and delete nothing or a lot more than expected. I’d be inclined to put an extension like DeleteByExample around the template version and maybe DeleteById on the other to make it clear what is going to happen.

Also note that we’re using the Collection again, so it is used for both Save() and Delete().

The NoRM interface provides all the capabilities we wanted out-of-the-box but can intrude a bit into our domain objects in order to support advanced queries. There’s a lot more to play with in this library so do give it a whirl.