Thursday, January 17, 2013

RavenDB And Wrapping Up

Last, but not least, we get to RavenDB. Here is that implementation of my code...


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Raven.Client;
using Raven.Client.Document;
using Raven.Abstractions;
using Raven.Database;

namespace ConsoleRaven1
{
    class Program
    {
        static void Main(string[] args)
        {
            DocumentStore store = new DocumentStore();
            store.Conventions.IdentityPartsSeparator = "-";
            store.DefaultDatabase = "console1";
            store.Url = "http://ross-pc:8080";
            store.Initialize();

            var session = store.OpenSession();

            Console.Write("First name: ");
            string first = Console.ReadLine();

            Console.Write("Last name: ");
            string last = Console.ReadLine();

            Name input = new Name() { FirstName = first, LastName = last };
            int dupCount = (from s in session.Query<Name>()
                            where s.FirstName == first && s.LastName == last
                            select s).Count();

            if (dupCount != 0)
            {
                Console.WriteLine("{0} {1} is already on file", first, last);
                Console.WriteLine("dupCount = {0}", dupCount);
            }
            else
            {
                Console.WriteLine("Proceeding...");
                session.Store(input);
                session.SaveChanges();
                Console.WriteLine("{0} {1} has been added", first, last);
            }

            Console.WriteLine("Names include:");

            var names = from s in session.Query<Name>()
                        orderby s.LastName, s.FirstName
                        select s;

            foreach (Name name in names)
            {
                Console.WriteLine("{0} {1} {2}", name.Id, name.FirstName, name.LastName);
            }

            store.Dispose();
        }
    }

    class Name
    {
        public string Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

First of all, I used the "RavenDB.Client" NuGet package. There is also a package for an embedded server, which I believe is called "RavenDB.Embedded".

This Name class is the cleanest of the 3. No data annotations were needed. By default, RavenDB IDs are structured as Class/Number, like "Name/33". You can make that more web-friendly by setting the IdentityPartsSeparator, as I did. So, the same document ID will be rendered "Name-33" in my code. You also noticed I specified a URL. You could probably use the name of any computer you have network access to. Initializing starts the server instance with the settings you used. You need a Session in order to actually access your data. Disposing the data store helps clean up memory.

If you're familiar with the 2008 (or more recent) version of the .NET Framework, the LINQ should be recognizable. The API calls for querying are minimal. Plus, the data object doesn't need to be massaged to be stored.

RavenDB is NOT a Windows Service. You have to manually start and stop the server, unless you you want to embed its functionality into your project. The nice thing is RavenDB comes with a browser-based administration console, which was been recently redesigned. If the server is running, just enter the same url as in the code and log in like you would Windows.

Couchbase has one as well, and you can query CouchDB using URLs if you know the right syntax. "http://localhost:5984/console1/_design/names/_view/all" is an example. MongoDB uses a command-line EXEs  for administration.

Well, that's all for now. Ciao!    

Focus on MongoDB

Next, we come to MongoDB. From what I understand, it's one of the most popular NoSQL systems out there. Anyway, here is my code...


using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver.Builders;                // needed for the method Query 

namespace ConsoleMongo1
{
    class Program
    {
        static void Main(string[] args)
        {
            MongoServer server = MongoServer.Create();
            server.Connect();
            var db = server.GetDatabase("console1");
            var collect = db.GetCollection<Name>("names");

            Console.Write("First name: ");
            string first = Console.ReadLine();

            Console.Write("Last name: ");
            string last = Console.ReadLine();

            Name input = new Name() { FirstName = first, LastName = last };
            var query = Query.And(Query<Name>.EQ(n => n.FirstName, first), Query<Name>.EQ(n => n.LastName, last));
            long dupCount = collect.FindAs<Name>(query).Count();

            if (dupCount != 0)
            {
                Console.WriteLine("{0} {1} is already on file", first, last);
                Console.WriteLine("dupCount = {0}", dupCount);
            }
            else
            {
                Console.WriteLine("Proceeding...");
                collect.Save<Name>(input);
                Console.WriteLine("{0} {1} has been added", first, last);
            }

            Console.WriteLine("Names include: ");

            foreach (Name name in collect.FindAllAs<Name>())
            {
                Console.WriteLine("{0} {1} {2}", name.Id, name.FirstName, name.LastName);
            }
         
        }
    }

   
    class Name
    {
        [BsonId]
        public ObjectId Id { get; set; }
        [BsonElement("FirstName")]
        public string FirstName { get; set; }
        [BsonElement("LastName")]
        public string LastName { get; set; }
    }
}

For this program, I used the NuGet package "mongocsharpdriver", which involves a number of DLLs. Instead of JSON, MongoDB stores data as memory-mapped Bson. Note that I used an ObjectId document key instead of string. Also, I had to tag that field as BsonId.  

MongoDB uses 3 layers: Server, Database and Collection. Collections are analogous to relational database tables. Unlike CouchDB, anything that needs to be created automatically is. 

The query object I used was contained in the MongoDB.Driver.Builders namespace. I used the object-oriented domain object approach, using lambda functions to specify the search parameters. Feeding the query into the FindAs function located all the matches, which I then had Mongo count. FindAllAs located every Name object in the Collection. 

Unlike LoveSeat, this API allowed me to specify a type when I stored the user data. On the other hand, this driver needed more DLLs.  As in CouchDB, the document key was automically generated. However, in other architectures, you might need to recast the Mongo document IDs as Strings to keep the environment happy... like ASP.NET MVC, for example.   

Focus on CouchDB

Basically I created 3 parallel console solutions: ConsoleCouch1, ConsoleMongo1, and ConsoleRaven1. I was going to compare the 3 piece-by-piece, but I decided that would be too confusing for both of us. So, instead we'll examine the programs as wholes.

I'll tackle CouchDB first...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using LoveSeat;

namespace ConsoleCouch1
{
    class Program
    {
        static void Main(string[] args)
        {
            string DATABASE = "console1";
            string DESIGN_DOC = "names";
            CouchDatabase database;
            CouchClient client = new CouchClient();
        //    client.DeleteDatabase(DATABASE);
            if (!client.HasDatabase(DATABASE))
            {
                client.CreateDatabase(DATABASE);
                database = client.GetDatabase(DATABASE);

                string designDoc =
                    "{\"_id\":\"_design/" + DESIGN_DOC + "\"," +
                    "\"views\": { " +
                                       "\"all\": {" +
                                                      "\"map\": \"function(doc) {emit(null, doc)}\"" +
                                                "}," +
                                       "\"by_name\": {" +
                                                      "\"map\": \"function(doc) {emit([doc.FirstName, doc.LastName], doc)}\"" +
                                                     "}" +
                               "}" +
                   "}";
                database.SaveDocument(new Document(designDoc));
            }
            else
            {
                database = client.GetDatabase(DATABASE);
            } 
            database.SetDefaultDesignDoc(DESIGN_DOC);

            Console.Write("First name: ");
            string first = Console.ReadLine();

            Console.Write("Last name: ");
            string last = Console.ReadLine();

            Name input = new Name() { FirstName = first, LastName = last };

            var options = new ViewOptions();
            options.Key.Add(first);
            options.Key.Add(last);
            int dupCount = database.View<Name>("by_name", options).Items.Count();

            if (dupCount != 0)
            {
                Console.WriteLine("{0} {1} was already on file", first, last);
                Console.WriteLine("dupCount = {0}", dupCount);
            }
            else
            {
                Console.WriteLine("Proceed...");
                database.SaveDocument(new Document<Name>(input));
                Console.WriteLine("{0} {1} was added", first, last);
            }

            Console.WriteLine("Names include:");

            var names = database.View<Name>("all").Items;
            foreach (Name name in names)
            {
                Console.WriteLine("{0} {1} {2}", name._id, name.FirstName, name.LastName);
            }
        }
    }

    [JsonObject]
    class Name
    {
        [JsonProperty]
        public string _id { get; set; }
        [JsonProperty]
        public string _rev { get; set; }
        [JsonProperty]
        public string FirstName { get; set; }
        [JsonProperty]
        public string LastName { get; set; }
     }
}

I chose the LoveSeat API to interface with CouchDB, using the "loveseat" NuGet package. Note that it comes with its own version of Newtonsoft.Json, something to keep in mind if you use ASP.NET MVC. Note that I use _id and _rev strings, both necessary for if you need the full CRUD spectrum. Also note that I used Json attributes since CouchDB stores data as JSON. 

I had to specifically create the database; you'll get an error if you don't. Also, I created a design document, which stores the Javascript CouchDB uses to run queries. The first "map" function simply retrieves all the data documents. The second receives 2 strings (first name and last name), and retrieves all the documents whose data matches those strings. You need to tell CouchDB which design document to use.

To run a parameterized query,  you create a ViewOptions object and feed it the key values. You then run the View, telling it what class the documents are and giving it the ViewOptions object. The Items in the View contain the retrieved documents, which you then can treat as IEnumerable. If the map function uses "null" in the first parameter of its "emit", you don't need to submit any keys; it's like like a SQL statement without a WHERE.

To store a domain object, you first translate it into JSON, hence the Document creation. The SaveDocument method expects a JSON object. 

If you think some of this sounds painful, but you don't want to give up on CouchDB entirely, look into Couchbase, which I might explain in a future blog. 

Thursday, January 10, 2013

Installing And Running Servers

Before we get into the code, we're going to discuss some housekeeping. I'm going to assume you're using Windows.

First, I'll mention CouchDB. You can download the latest version from http://couchdb.apache.org. Select the version you want to use, then select a mirror. Download and run the program, and you should be set to go.

You can find MongoDB at www.mongodb.org/downloads . Select the appropriate build, then decompress the Zip file. Getting Mongo to work is a little more effort. First, you have to mkdir C:\data\db at the command prompt, which you'll need to run as Administrator. The next step is to go into the bin folder of the decompressed folder, and mongod --install --logpath log. You can use a different logpath if you want. Go into Control Panel and make sure the MongoDB service is installed and running.     

RavenDB is at http://ravendb.net/download#builds. Select whether you want the latest builds. Select a build, then click "RavenDB". Unpack the Zip file. To run the server, go into the decompressed folder, go into the "Server" folder, and double-click the application where the black bird is. A console window will open. To shut the server down, enter a "q" command. Also, RavenDB comes with a server web console; the URL is your computer's name (like "Ross-PC" in my case) at port 8080. The DOS window should tell you what the URL is; write that down, as you will need it later.

Once you have the servers running, you're ready to run some code. We'll be diving into the code in the next installment.

Wednesday, January 9, 2013

Introduction

This is Ross Albertson; I'm back again! This time, I'm coding a simple C# console application three ways, using Raven, Couch (the LoveSeat driver) and Mongo. I'll be using Visual Studio 2012 and NuGet for my environment. This program has a simple purpose, namely adding people's names to a database, but avoiding duplicates. I hope you find this blog useful. I won't be diving into the details just yet; I'm just letting you know what's to come.