danielwertheim

danielwertheim


notes from a passionate developer

Share


Sections


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

Get up and running with CouchDb and C# using MyCouch on Windows

This will show you how easy it is to get CouchDb up and running on a Windows machine. I will show you how to use the builtin admin tool to secure the server as well as the database that we will create. I will then show you how easy it is to interact with it using a C# client called, MyCouch.

Install CouchDb (5min)

  1. Download CouchDb from the official site

  2. Select the Windows download, I selected: Windows (x86) Erlang/OTP R15B03-1 | Version 1.3.0.

gurwcam-download

  1. After download is complete, run it. I went with the provided default options:

gurwcam-install1

gurwcam-install2

  1. Test it. Fire up a browser and head over to: http://localhost:5984. You will be presented with something similar to this:
{
    "couchdb": "Welcome",
    "uuid": "c9e34dba2a5cc89e63cbd96be61c7529",
    "version": "1.3.0",
    "vendor": {
        "version": "1.3.0",
        "name": "The Apache Software Foundation"
    }
}

Well done, first task completed!

Use the admin tool to secure the server and our test database

This chapter shows you more than what is required just to test things out, so if you are not interested in the security parts, just ensure you have created the test database before continuing to the next chapter.

CouchDb comes with a web based admin tool called Futon. You find it by navigating to: http://localhost:5984/_utils/

gurwcam-hellofuton

Here you can manage your CouchDb installation like securing it, adding users, creating databases, designing views, managing documents in the database etc. What is important to note is the little text in the right corner:

Welcome to Admin Party!
Everyone is admin. Fix this

If you click the "Fix this" link, you will be presented with a dialog where you can create a server admin. Read up on this.

gurwcam-createserveradmin

Now, log out and then try to create a database:

gurwcam-createdbnotallowed

Nice! At least somewhat more secure. You should also note that CouchDb has support for activating SSL.

Login to Futon again and create a database called "test". And the press the Security icon. You should be presented with:

gurwcam-dbsecurity

Here you can specify specific users or roles by providing either user names or roles for:

The user names and roles should match credentials for users stored in the _users database (open). In there the server admin user already exists. Lets create two new users. One that will be database admin for our test database and one that will be a simple member of it.

gurwcam-createdba

{
   "_id": "org.couchdb.user:danielwertheim1",
   "name": "danielwertheim1",
   "password": "testtest1",
   "roles": ["dba"],
   "type": "user"
}

gurwcam-createmember

{
   "_id": "org.couchdb.user:danielwertheim2",
   "name": "danielwertheim2",
   "password": "testtest2",
   "roles": [],
   "type": "user"
}

The passwords will NOT be stored in clear text, but will be encrypted upon saved. Also note that the first user was assigned a role dba while the second user wasn’t assigned any roles.

Now, head back to the test database and click the security icon and fill in the form:

gurwcam-securethedb

Voila! It’s now secured. But again, you really should look into the link above regarding SSL, since CouchDb uses HTTP as transport protocol, hence credentials will be transmitted unsecured.

HTTP you say

Yes, CouchDb uses HTTP as its transport protocol and it is REST based, hence you could easily interact with it using any HTTP enabled client. My favorite tool for this is: Postman; which is a simple extension to Google Chrome.

Let me show you have to insert a simple document representing an artist using Postman. The JSON for the document looks like this:

{
    "_id":"1",
    "$doctype":"artist",
    "name": "Super duper artist",
    "albums":[
        {"name": "Super duper album #1", "tracks": 12},
        {"name": "Super duper album #2", "tracks": 10}
    ]
}

_id is a CouchDb reserved field that holds the id of the document. I’ve also added a field $doctype which is not a CouchDb field, but something I use to indicate what kind of entity the document represents. This is useful when it comes to authoring views (as we will see later on). Some people include this info in the _id: "_id":"artist:1"; but that will increase the _id size which will have negative effects on sizes of your views etc., since they always outputs the _id.

Time to try it out. Execute a POST against http://localhost:5984/test/ where test addresses the database we created before.

gurwcam-postmanunauthpost

Notice the response. We secured the database hence we need to provide credentials for an user that is a member. I will use basic authentication for this.

gurwcam-postmanbasicauth

Now if we retry the POST it shall succeed.

gurwcam-postmanauthpost

Pay attention to the response. It’s an 201 and it has the _id but also a new reserved CouchDb field, the _rev field. That field will be assigned a new unique value every time the document is changed. And that is how CouchDb handles concurrency checking etc. To update or delete a document, you need to provide the latest matching _rev. If not, CouchDb will not accept your update and will note you about it. Lets try it. Lets do a PUT to add a new album to the previously added artist. Execute a PUT against the following address:

http://localhost:5984/test/1?rev=1-6474d7cfb6040bdc24d697d013c9e868

And provide the following document:

{
    "_id":"1",
    "$doctype":"artist",
    "name": "Super duper artist",
    "albums":[
        {"name": "Super duper album #1", "tracks": 12},
        {"name": "Super duper album #2", "tracks": 10},
        {"name": "Super duper album #3", "tracks": 2}
    ]
}

Note that the address now points to the document with _id being 1 in the database having the name test. We also include the current _rev, and then a new document. Since CouchDb does not work with partial document updates, we have to provide the complete document. The response is an 201 with the following content:

{
    "ok": true,
    "id": "1",
    "rev": "2-8b8e0ed734dad8f7175bde5e6bff1161"
}

There are solutions you can implement to work around the fact that CouchDb updates the whole document. Read about "Update handlers".

If you try and execute the exact same request again, your request will not be accepted. This, since you are not using the latest known _rev. The response will be and 409 Conflict with the following content:

{
    "error": "conflict",
    "reason": "Document update conflict."
}

If you would like to get the document, just execute a GET using the following address: http://localhost:5984/test/1; and you will get the document in response.

gurwcam-postmanget

Views

Lets take a crash course in CouchDb views. For more in depth material I recommend you to go through these resources:

While you were fetching a coffee or reading some Twitter feeds our Facebooking, I took the liberty to add two more artists with some more albums so the database now contains a total of three artists. Now, lets look at them using a view. Before we create our own view, lets use a built in system view called all_docs. Execute a GET against the following address: http://localhost:5984/test/_all_docs. The response will be something like this:

{
    "total_rows": 3,
    "offset": 0,
    "rows": [
        {
            "id": "1",
            "key": "1",
            "value": {
                "rev": "2-8189ea9db5d99febbdff578aaa15f08d"
            }
        },
        {
            "id": "2",
            "key": "2",
            "value": {
                "rev": "1-9dbcefdd8a022731f10e693d7327d3dc"
            }
        },
        {
            "id": "3",
            "key": "3",
            "value": {
                "rev": "1-8efdf5f4c63b3f23ad48250450f13f4f"
            }
        }
    ]
}

There’s a special flag you can include to force the view to also load in the docs with the result. Do the same request and this time append include_docs=true in the query string.

{
    "total_rows": 3,
    "offset": 0,
    "rows": [
        {
            "id": "1",
            "key": "1",
            "value": {
                "rev": "2-8189ea9db5d99febbdff578aaa15f08d"
            },
            "doc": {
                "_id": "1",
                "_rev": "2-8189ea9db5d99febbdff578aaa15f08d",
                "$doctype": "artist",
                "name": "Super duper artist",
                "albums": [
                    {
                        "name": "Super duper album #1",
                        "tracks": 12
                    },
                    {
                        "name": "Super duper album #2",
                        "tracks": 10
                    },
                    {
                        "name": "Super duper album #3",
                        "tracks": 2
                    }
                ]
            }
        },
        {
            "id": "2",
            "key": "2",
            "value": {
                "rev": "1-9dbcefdd8a022731f10e693d7327d3dc"
            },
            "doc": {
                "_id": "2",
                "_rev": "1-9dbcefdd8a022731f10e693d7327d3dc",
                "$doctype": "artist",
                "name": "Super bad artist",
                "albums": [
                    {
                        "name": "Super bad album #1",
                        "tracks": 77
                    }
                ]
            }
        },
        {
            "id": "3",
            "key": "3",
            "value": {
                "rev": "1-8efdf5f4c63b3f23ad48250450f13f4f"
            },
            "doc": {
                "_id": "3",
                "_rev": "1-8efdf5f4c63b3f23ad48250450f13f4f",
                "$doctype": "artist",
                "name": "Awesome artist",
                "albums": [
                    {
                        "name": "Awesome album #1",
                        "tracks": 3
                    },
                    {
                        "name": "Awesome album #2",
                        "tracks": 10
                    }
                ]
            }
        }
    ]
}

You can also access this view via Futon. Just open the following URL in your browser: http://localhost:5984/_utils/database.html?test/_all_docs

gurwcam-futonalldocs

Create a custom view

Lets have a look at creating a simple custom view that only will output the albums of an artist. You can do this using Futon. Open: http://localhost:5984/_utils/database.html?test/_temp_view and type in the following map function.

function(doc) {
  if(doc.$doctype !== 'artist')
    return;

  emit(doc.name, doc.albums);
}

gurwcam-futonnewview

If I chose to run the view, I get presented by the view result. Have in mind though that running a temporary view will traverse all your documents. Doing this is resource intensive, hence only suitable for development environments. For production use, you should store your views in a design document (a design document can hold many views).

Lets say we are happy with this view and want to "publish" it by making it part of a design document. Then all we have to do is to press the Save As....

gurwcam-futonsaveview

The view is now saved as part of the design document but yet not "materialized". The view will be materialized the first time it is queried. This goes for all the vies in the same document:

To change a view or multiple view just alter the design document they are stored in and save it as a new revision. This causes all the views in that design document to be rebuilt on the next access in case the view code has been changed – http://wiki.apache.org/couchdb/HTTPviewAPI

Some other readings in the subject of design documents and views:

Consume the view

Lets just query the view as it is, without limiting the result. Execute a GET against http://localhost:5984/test/_design/artists_albums/_view/albums_by_artist

{
    "total_rows": 3,
    "offset": 0,
    "rows": [
        {
            "id": "3",
            "key": "Awesome artist",
            "value": [
                {
                    "name": "Awesome album #1",
                    "tracks": 3
                },
                {
                    "name": "Awesome album #2",
                    "tracks": 10
                }
            ]
        },
        {
            "id": "2",
            "key": "Super bad artist",
            "value": [
                {
                    "name": "Super bad album #1",
                    "tracks": 77
                }
            ]
        },
        {
            "id": "1",
            "key": "Super duper artist",
            "value": [
                {
                    "name": "Super duper album #1",
                    "tracks": 12
                },
                {
                    "name": "Super duper album #2",
                    "tracks": 10
                },
                {
                    "name": "Super duper album #3",
                    "tracks": 2
                }
            ]
        }
    ]
}

CouchDb lets you "filter" your views in a variety of ways (http://wiki.apache.org/couchdb/HTTPviewAPI). Lets do a quick test. Lets just target one row. The row which has a key being "Super bad artist". Easy, just append key to the query string and provide an URL encoded value. Execute a GET against http://localhost:5984/test/_design/artists_albums/_view/albums_by_artist?key="Super bad artist"

{
    "total_rows": 3,
    "offset": 1,
    "rows": [
        {
            "id": "2",
            "key": "Super bad artist",
            "value": [
                {
                    "name": "Super bad album #1",
                    "tracks": 77
                }
            ]
        }
    ]
}

That was a brief tour on views. And even though we only scratched the surface of views and only used map functions, I hope it was enough for this introductory post, and let us now switch over to using the C# driver.

Introducing MyCouch

MyCouch is a simple async, open sourced, CoucDb client written in C#. It builds on top of the async HttpClient and lets you work with native JSON and/or entities/POCOs. With that said, remember, CouchDb uses HTTP as a protocol, hence if you find something missing or not acting as you want it, you could easily acomplish what you want using pure http-requests. MyCouch tries to keep the domain language of CouchDb and not hiding or abstracting away the fact that it is http based.

Install it

Lets have a quick look at how we would accomplish some of the steps we have done above. For simplicity I have just created a simple Console application using Visual Studio 2012 (there is a sample console app in the GitHub repository). MyCouch is distributed via NuGet. So to install it, open the Package Manager Console and invoke:

PM> Install-Package MyCouch

Now, lets start typing some code. The first task is to add yet another artist. What you use to communicate with CouchDb in MyCouch, is an implementation of MyCouch.IClient. There’s one, MyCouch.Client which accepts either a custom connection or a simple URL/Uri pointing to your database (read more).

static void Main(string[] args)
{
    using (var db = new MyCouch.Client("http://danielwertheim1:testtest1@localhost:5984/test"))
    {
        //Consume client here
    }
}

Since we have secured our database, I somehow need to pass the credentials. Here, I have chosen to do it using Basic http authentication (read more).

Documents

MyCouch lets you work with native JSON documents or entities/POCOs (read more) and you can either run it synchronously or asynchronously (read more). Lets start with inserting an artist using JSON. Inserting is done using a POST or PUT (according to docs, PUT is recommended).

var artistJson = "{"_id": "4",
                   "$doctype": "artist",
                   "name": "Fake artist 1",
                   "albums":[{"name": "Greatest fakes #1", "tracks": 3}]}";

var response = db.Documents.Post(artistJson);

//for async: db.Documents.PostAsync(artistJson);

Console.Write(response.GenerateToStringDebugVersion());

Every command in MyCouch will provide you with a response object in turn. The response will look a bit differently depending on what command you executed (read more).

RequestUri: http://localhost:5984/test/
RequestMethod: POST
Status: Created(201)
Error:<NULL>
Reason: <NULL>
Id: 4
Rev: 1-2cc0876b59cadf2e0271f03a7ea54c5c

To update the artist, lets say by adding a new album, you invoke a PUT:

var artistJson = "{"_id": "test:4",
                   "$doctype": "artist",
                   "name": "Fake artist 1",
                   "albums":[
                       {"name": "Greatest fakes #1", "tracks": 3},
                       {"name": "Greatest fakes #2", "tracks": 6}]}";

var response = db.Documents.Put("5", "1-2cc0876b59cadf2e0271f03a7ea54c5c", artistJson);

Console.Write(response.GenerateToStringDebugVersion());

The response:

RequestUri: http://localhost:5984/test/4?rev=1-2cc0876b59cadf2e0271f03a7ea54c5c
RequestMethod: PUT
Status: Created(201)
Error:<NULL>
Reason: <NULL>
Id: 4
Rev: 2-9d756cc83e3937f953648032ef8629bb

How about conflicts

Well, lets try it. Invoke the exact same PUT as we just did and lets inspect parts of the response:

Status: Conflict(409)
Error:conflict
Reason: Document update conflict.

Entities

Before looking at using views to query data, lets have a quick look at the Entity API. To insert a new artist like we did above, you would first need a model (read more about member names for _id and _rev etc).

public class Artist
{
    public string ArtistId { get; set; }
    public string ArtistRev { get; set; }

    public string Name { get; set; }
    public Album[] Albums { get; set; }
}

public class Album
{
    public string Name { get; set; }
    public int Tracks { get; set; }
}

Now, you could just use it to work with entities, e.g doing the same as we did with JSON previously.

var artist = new Artist
{
    ArtistId = "5",
    Name = "Foo bar",
    Albums = new[]
    {
        new Album { Name = "Hello world!", Tracks = 9 }
    }
};

var response = db.Entities.Post(artist);

Console.Write(response.GenerateToStringDebugVersion());

The response:

RequestUri: http://localhost:5984/test/
RequestMethod: POST
Status: Created(201)
Error:<NULL>
Reason: <NULL>
Id: 5
Rev: 1-725e193084247fcf7cc84e4cfa179746
IsEmpty: False
Model: Artist

In the JSON sample that we saw previously, we explicitly specified a meta field $doctype. When you work with entities, MyCouch will handle this for you, and it will be default use the name of the class as the value for $doctype. When working with entities, MyCouch will, on successful operation, extract e.g the _rev from the response and assign it to the entity, which also is returned via the response property: response.Entity.

Views

Apart from using MyCouch to perform simple and fast GET by id requests, you can also use it to query views (read more). There are different ways of doing this, but I will only show one way. But do not fear, it’s documented. But before querying the view, lets look at how we could create the view we previously created using Futon, but this time using MyCouch.

Creating a view

A view is, as previously stated, part of a design document which is a plain document; hence you can create one by just inserting a document. Lets have a look. First lets define it:

public static class Views
{
    public const string ArtistsAlbums =
        "{" +
            ""_id": "_design/artists_albums"," +
            ""language": "javascript"," +
            ""views": {" +
                ""albums_by_artist": {" +
                    ""map": "function(doc) {  if(doc.$doctype !== 'artist') return;  emit(doc.name, doc.albums);}"" +
                "}" +
            "}" +
        "}";
}

Now we could just use the client and POST or PUT it into the db:

db.Documents.Post(Views.ArtistsAlbums);

Remember, that you will get a conflict error if you didn’t delete the view we created before.

Query the view

Now that the view is in place, lets consume it. Views are accessed via the View API, hence using db.Views. Queries can be executed in two ways, via: RunQuery or Query. The difference in short:

Query<>() method will create a query and run the query for you using RunQuery. RunQuery lets you work freely with any IViewQuery, so that you could run the same query against multiple databases.

You can use a built in chained-configure API to configure your queries, or directly assign values to query.Options. I’ll use the former:

gurwcam-mycouchrunquery

As you can see, there are a bunch of query capabilities and you can read about them in the documentation. I’ll go for a simple Key filter.

var query = new MyCouch.Querying.ViewQuery("artists_albums", "albums_by_artist");
query.Configure(cfg => cfg.Key("Fake artist 1"));

var response = db.Views.RunQuery<Album[]>(query);

Console.Write(response.GenerateToStringDebugVersion());

The response:

RequestUri: http://localhost:5984/test/_design/artists_albums/_view/albums_by_artist?key="Fake artist 1"
RequestMethod: GET
Status: OK(200)
Error:<NULL>
Reason: <NULL>
IsEmpty: False
TotalRows: 5
RowCount:1
Offset: 1

So you get some meta data that could be used for paging etc, but where are the albums? They are in the response.Rows.

gurwcam-mycouchentityqueryresult

Of course you could get the result as JSON if you want:

gurwcam-mycouchjsonqueryresult

Or as a JSON-array:

gurwcam-mycouchjsonarrayqueryresult

Summary

That’s it for now. I hope it wasn’t to overwhelming. I do realize I probably left you with a bunch of question but I hope you could find some of the answers in the project wiki. If not, just drop a comment here or ping me on Twitter: @danielwertheim or tweet with the hashtag #mycouch.

Cheers,

//Daniel

View Comments