Getting Started With MongoDB and NoRM
09 Jul 2010I just realised - I hate writing SQL. I hate it, I hate it, I hate it. I have also recently noticed a growing trend in SQL alternatives or “nosql” with open source C# drivers. Today I put one and one together and decided to try out one of these – MongoDB using the C# driver NoRM. Why this idea didn't occur to me earlier I will never know.
What is MongoDB?
MongoDB is one of a growing number of nosql databases. In short these databases are non relational and schema free. I won’t attempt to give a full rundown of the pros and cons of nosql but the stated benefits are generally saleability, performance and simplicity.
MongoDB is a document orientated database storing data on disk as binary json. Some key features include indexing, rich queries, map reduce and horizontal scalability.
The Mongo website sums it all up much better than I do so you should probably just go there:
MongoDB bridges the gap between key-value stores (which are fast and highly scalable) and traditional RDBMS systems (which provide rich queries and deep functionality).
MongoDB (from "humongous") is a scalable, high-performance, open source, document-oriented database.
For installation instructions for Mongo I recommend taking a look at the Windows Quickstart guide.
What (or who) is NoRM?
NoRM is an open source .Net library for connecting to MongoDB. Its main drawcards for me are:
- NoRM is strongly typed. It uses plain .net classes when querying and updating the database.
- Uses linq for queries
- Simple and convention based. NoRM makes it really simple to get persistence up and running as I will soon demonstrate.
To get NoRM head over to http://github.com/atheken/NoRM.
Using NoRM in C#
I’m going to be using NoRM with a pretty standard session pattern. My MongoSession will implement the following ISession interface which uses linq expressions for queries.
public interface ISession : IDisposable { void CommitChanges(); void Delete<T>(Expression<Func<T, bool>> expression) where T : class, new(); void Delete<T>(T item) where T : class, new(); void DeleteAll<T>() where T : class, new(); T Single<T>(Expression<Func<T, bool>> expression) where T : class, new(); System.Linq.IQueryable<T> All<T>() where T : class, new(); void Add<T>(T item) where T : class, new(); void Add<T>(IEnumerable<T> items) where T : class, new(); void Update<T>(T item) where T : class, new(); }
The MongoSession class is based off the one found in the mvcstarter project and uses NoRM to implement ISession. The constructor for this class sets up the Mongo connection looking for a web.config connection string entry named “db”. An example connection string is given after the code.
public class MongoSession : ISession { private Mongo _provider; public MongoDatabase DB { get { return this._provider.Database; } } public MongoSession() { //this looks for a connection string in your Web.config _provider = Mongo.Create("db"); } public void CommitChanges() { //mongo isn't transactional in this way } public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() { var items = All<T>().Where(expression); foreach (T item in items) { Delete(item); } } public void Delete<T>(T item) where T : class, new() { DB.GetCollection<T>().Delete(item); } public void DeleteAll<T>() where T : class, new() { DB.DropCollection(typeof(T).Name); } public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new() { return All<T>().Where(expression).SingleOrDefault(); } public IQueryable<T> All<T>() where T : class, new() { return _provider.GetCollection<T>().AsQueryable(); } public void Add<T>(T item) where T : class, new() { DB.GetCollection<T>().Insert(item); } public void Add<T>(IEnumerable<T> items) where T : class, new() { foreach (T item in items) { Add(item); } } public void Update<T>(T item) where T : class, new() { DB.GetCollection<T>().UpdateOne(item, item); } //Helper for using map reduce in mongo public T MapReduce<T>(string map, string reduce) { T result = default(T); MapReduce mr = DB.CreateMapReduce(); MapReduceResponse response = mr.Execute(new MapReduceOptions(typeof(T).Name) { Map = map, Reduce = reduce }); IMongoCollection<MapReduceResult<T>> coll = response.GetCollection<MapReduceResult<T>>(); MapReduceResult<T> r = coll.Find().FirstOrDefault(); result = r.Value; return result; } public void Dispose() { _provider.Dispose(); } }
<connectionStrings> <add name="db" connectionString="mongodb://localhost/testdb?strict=true"/> </connectionStrings>
One of the best things about Mongo and NoRM is that NoRM will store plain and simple .net objects. This means we can define our models completely in code. Because Mongo is schema less we can also add and remove properties without any problems.
I am going to store a simple Trip class for a scheduling application. There are a couple of things to note about this code, first is the [MongoIdentifier] Attribute. Mongo requires collections have a unique identifier. When using NoRM the options you can use are: Guid/UUID, int, or ObjectId. The property must also be named either _id or Id. NoRM will handle generating the identifier when it is required. To keep my example simple I am using an integer. For more complete guidelines check the BSON Serializer page.
public class Trip { [MongoIdentifier] public int? Id { get; set; } public string Name { get; set; } public DateTime Start { get; set; } public int Duration { get; set; } public Trip() { Start = DateTime.Now; } }
The code to store the Trip class is very simple. The session’s add method will automatically store the object in the database as well as assigning it an Id.
var trip = new Trip() { Name = "test trip", Duration = 5, Start = DateTime.Now }; using (var session = new MongoSession()) { session.Add(trip); }
BAM! Here is the object I just saved viewed in the mongo shell. It has been assigned an identifier and stored in a collection called Trip. There was no need to define the structure of the object or to create a collection to store it in, this all happens automatically and is part of what makes coding with Mongo so refreshing.
Finding the document with code is simple using the linq methods of MongoSession.
using (var session = new MongoSession()) { var trip = session.Single<Trip>(t => t.Name == "test trip"); }
This is obviously a very basic overview of programming with Mongo and NoRM and therefore I have skipped over some of the more advanced features. NoRM’s linq provider is pretty good but on complex queries you may run into some issues. NoRM also has some configuration code you can add to optimise the way your objects are stored. Overall I am finding working without the constraints of a schema and letting your code define your data storage is a great way to program.