code first, ask questions later

Getting Started With MongoDB and NoRM

I 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.

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.

shell

 

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.

blog comments powered by Disqus