• Using MongoDB from C#

    Published by on January 29th, 2013 6:44 pm under c#, DB, Distributed databases, MapReduce, MongoDB, NoSQL, ORM

    3 Comments

    Today I’ve been playing a little with MongoDB, and after enjoyed triggering a few commands from Mongo’s console, I decided to download, build and try the C# driver. Build it and use it is pretty straightforward, and in a couple of minutes you can be playing with your local database from a C# console application.

    So I created a simple console application, that inserts and gets a couple of books from a DB collection in minutes with just very few simple steps. First at all you need to get a client instance and a server reference from C#, and using the driver you can do it like this:

    // connect to localhost and get server
    var server = new MongoClient().GetServer();
    var db = server.GetDatabase("mauro");
    
    // get book collection and clean it
    var books = db.GetCollection("books");
    

    From now on you can start performing operations in your books collection, for example adding a new book:

    books.Insert(
     new BsonDocument
     {
       { "Authors", new BsonArray { "Ernest Hemingway" } },
       { "Title", "For Whom the Bell Tolls" },
       { "Price", 31.53 }
     });
    

    To learn about the BsonDocument, BsonArray and other types related to this DB, browse its information here.

    If you are wondering why the keys for the JSON object are capitalized, you will find the answer in just a few  seconds, when a C# Book class will be introduced and used with the driver. This is an easy way to work with your domain objects as you would do if you were using an ORM. In this case I decided to follow C# convention when naming class properties (except for the _id used in Mongo) as you can see below:

    public class Book
    {
     public BsonObjectId _id { get; set; }
     public string Title { get; set; }
     public string[] Authors { get; set; }
     // decimal is not well-converted by the driver
     public double Price { get; set; }
    }
    

    Now that we have a Book class defined we can use it within the driver calls and work with this typed domain object. If you use the db.GetCollection<TDocumentType>() getter when instancing a collection of entities you will tell the driver that you want to wrap the BSON objects (managed under the hood) as TDocumentType objects:

    var typedBooks = db.GetCollection<Book>("books");
    

    Now you can make iterate all the elements in this collection; each element returned as a Book instance:

    Console.WriteLine("\nRetrieving all books (typed collection)");
    foreach (var book in typedBooks.FindAll())
    {
       Console.WriteLine(string.Format("Book retrieved: {0}", book));
    }
    

    And, of course, you can also add a new Book instance to the collection in a more easy way that having to deal with ‘JSON’ in C#:

    typedBooks.Insert(new Book
     {
       Title = "Seven Databases in Seven Weeks",
       Authors = new string[] { "Eric Redmond", "Jim R. Wilson" },
       Price = 35.67
     })
    

    At this point all these things are pretty much what any DB can offer (in addition to the ORM features we have from the driver), but what about using a little of MapReduce?

    Can I use it from C#? Yes!

    Let’s say that you want to sum the prices of all books in your collection. Well that’s something that you can very easily do with just a single line in C# using LINQ:

    var allPrices = typedBooks.FindAll().Sum(b => b.Price);
    Console.WriteLine(string.Format("Sum of all prices [linq]: ${0}", allPrices));
    

    The problem with this? You are transferring all the data to the client, all the books in your collection. Think what will be the cost if this collection is huge! In addition, this is a simple process that just sum values, but what if the logic would be more complex and require more processing power to obtain the result in a reasonable time?

    Fortunately, you can take advantage of the MapReduce engine MongoDB provides, besides being this the correct way to perform this kind of operations (over all your dataset). So let’s try MapReduce from C#.

    First at all, I created a static class to put there my Javascript functions (as MongoDB talks Javascript):

    public static class JavascriptFunctions
    {
     public static string MapBuildAllPricesKey
     {
       get
       {
          return "function(obj) { return 'all'; }";
       }
     }
    
    public static string MapAllPrices
     {
       get
       {
          return @"function() { emit(buildAllPricesKey(this), this.Price); }";
       }
     }
    
    public static string ReduceAllPrices
     {
       get
       {
          return @"function(key, values) {
             var allPrices = 0;
             for (var i=0; i<values.length; i++) {
                allPrices += values[i];
             }
             return { key: key, books: values.length, total: allPrices };
          }";
       }
     }
    }
    

    How can I use this functions now?

    Well, the MongoCollection provides a MapReduce() method that you can use to instruct the server what are the map and reduce operations you want to perform over a collection.

    In addition to just try MapReduce, I’ve also decided to take advantage of the possibiliy to store a function in the server (kind of stored procedure in RDBMS) and, as you can see, I’m planning to use it within the MapAllPrices() js function. Here’s the code to store a function in the server that can be called from other functions you upload:

    db.GetCollection("system.js").Save(
     new BsonDocument
     {
       { "_id", "buildAllPricesKey" },
       { "value", new BsonJavaScript(JavascriptFunctions.MapBuildAllPricesKey) }
     });
    

    But how do we finally sum all prices using MapReduce? Easy, just like this:

    var mr = books.MapReduce(JavascriptFunctions.MapAllPrices, JavascriptFunctions.ReduceAllPrices);
    Console.WriteLine(string.Format("Sum of all prices [map/reduce]: ${0}", mr.GetResults().First()["value"]["total"]));
    

    The mr variable contains the results of your MapReduce call, you can iterate its elements using LINQ (if the result values are many), here we are just taking the First() as we have only one.

    You can find the complete sample here.

    Tags: , , , , , ,