Skip to content
This repository has been archived by the owner on Sep 17, 2023. It is now read-only.

Documentation

Daniel Wertheim edited this page Mar 16, 2018 · 118 revisions

Prerequisites

Basically you need two things:

  • Access to a running CouchDB server or Cloudant hosted database.
  • An app that can run .Net code (.Net4.5+, Windows store NetCore4.5.1, ...) so that you can use the NuGet MyCouch.

Using Cloudant

From v5.0.0 the Cloudant speficic client has been removed.

From v0.17.0 and til (v.4.0.0) there is a Cloudant specific NuGet package that provides support for some Cloudant specific features like support for Lucene searches. The Cloudant-client also supports whatever the normal client does. But instead of e.g. var client = new MyCouchClient(...) you instead use var client = new MyCouchCloudantClient(...).

External resources, Tips, Samples etc

Feel free to add stuff or provide links to list here.

Install CouchDB and Getting started with MyCouch

It really is a breeze. Takes like 5min to get a single node up and running. Just head over to CouchDB and download an installation package and install it. There's an blog post covering the setup and getting started with MyCouch on Windows: "Get up and running with CouchDB and C# using MyCouch on Windows"

Understand HttpClient Scope

Each MyCouch-client uses one HttpClient, please read this for more info regarding scoping/lifetime.

Install using NuGet

MyCouch is distributed via NuGet. Just type:

install-package mycouch

And from v0.17.0 there's a Cloudant specific NuGet package that brings support for some additional features like Lucene searches.

install-package mycouch.cloudant

Get connected

After installing the NuGet, just create an instance of MyCouch.Client and pass it the URL of your server and database.

using (var client = new MyCouchClient("http://127.0.0.1:5984", "testdb"))
{
    //Consume here
}

The format is: {scheme}://[{username}:{password}]/{authority}/.

Authentication

From v0.2.0 you can just pass username and password via the URL passed to the client.

The username and password will be extracted and decoded using Uri.UnescapeDataString and then turned to a base64 encoded string which is passed via the HTTP-Header Authorization.

If you have a username or password that needs to be encoded, you will either have to explicitly define it:

//char @ = %40
new MyCouchClient("http://someuser:p%40ssword@localhost:5984", "testdb")

You can also pass a DbConnectionInfo instance if you want, and on it assign BasicAuth, which is what is happening underneath.

Contextual client

The Client is designed to mimic CouchDb terminology as much as "possible", hence it uses names as Post & Put instead of Insert & Update. It also returns responses and not JSON and/or entities directly. You thereby get a chance to get status, errors, reasons and other metadata as the RequestURI, _id, _rev etc.

The Client is "divided" in contexts of: Databases, Documents, Entities & Views; where each "grouping" contains operations related to them. So, lets say we would like to insert a new document, then you would use any of the POSTs found under the Documents API.

client.Documents.PostAsync(json);

If you instead want to work with entities, you would instead use the Entities API:

client.Entities.PostAsync(myEntity);

Each Client instance uses one HttpClient instance (MSDN), which gets disposed when the client instance gets disposed.

MyCouchStore - an opinionated abstraction/simplification

Within the MyCouch library there is a MyCouchStore : IMyCouchStore, which wraps an IMyCouchClient and hides the HTTP-requests and HTTP-responses and instead lets you work directly with JSON and Entities. And instead of having to inspect the status of HTTP-responses, it throws exception (MyCouchResponseException) if the response doesn't succeed.

using(var store = new MyCouchStore(serverUri, dbName))
{
    var docHeader = await store.StoreAsync(json);
    var entity = await store.StoreAsync(entity);

    var json = await store.GetByIdAsync(someId);
    var customer = await store.GetByIdAsync<Customer>(someId);

    var exists = await store.ExistsAsync(id, [rev]);

    var header = await store.GetHeaderAsync(id, [rev]);

    var queryInfo = await store.QueryAsync(query, callbackForEachRow);

    var rows = await store.QueryAsync(query);

    ///and so on...
}

MyCouchServerClient

Is used to access a server node rather then a specific database. You can use it to create databases, perform replication etc.

MyCouchServerClient.IDatabases

Groups database oriented operations.

using(var client = new MyCouchServerClient("http://localhost:5984"))
{
    await client.Databases.PutAsync("mydb");
    await client.Databases.GetAsync("mydb");
    await client.Databases.HeadAsync("mydb");
    await client.Databases.DeleteAsync("mydb");
    await client.Databases.CompactAsync("mydb");
    await client.Databases.ViewCleanupAsync("mydb");
}

Replication

Accessed via ServerClient.Replicator.

using(var client = new MyCouchServerClient("http://localhost:5984"))
{
    await client.Replicator.ReplicateAsync("sourceDb", "targetDb");
    //OR
    var request = new ReplicateDatabaseRequest("sourcedb", "targetdb")
    {
        Proxy = "http://...",
        Continuous = true,
        CreateTarget = true,
        DocIds = new[] { "d1", "d2" },
        Filter = "designdoc/filter"
    };
    await client.Replicator.ReplicateAsync(request);
}

Asynchronous

Underneath, the new async HttpClient found in .Net v4.5 (and .Net 4.0 via PCLs from Microsoft) is used. From v0.10.0 there's no longer any synchronous API provided, hence only an asynchronous API is provided. This since it's not recommended to provide a sync over async API, and also, it's really easy to e.g. await the API.

Modular

If you think the Client carries to much and want to compose your own, it's quite easy. Just pick the contextual module you want. Lets say you only want to work with native JSON and documents, you could make use of the MyCouch.Contexts.Documents class. You could easily have a look in the MyCouchClientBootstrapper to see how it's bootstrapped, or you could use the bootstrapper:

var bootstrapper = new MyCouchClientBootstrapper();

using(var cn = new DbConnection(connectionInfo))
{
    var documents = bootstrapper.DocumentsFn(cn);
}

And if you think there's something funky going in in there; most functionality is located in factories for the HttpRequests as well as for the HttpResponses. These are located under MyCouch.HttpRequestFactories and MyCouch.Responses.Factories. So you could pick the factories you need to work with request and responses. Lets look at an sample:

public class Sample {
    private readonly IDbConnection _cn;
    protected GetDocumentHttpRequestFactory GetDocumentHttpRequestFactory { get; set; }
    protected DocumentResponseFactory DocumentReponseFactory { get; set; }

    public Sample(IDbConnection cn, ISerializer serializer){
        _cn = cn;
        GetDocumentHttpRequestFactory = new GetDocumentHttpRequestFactory();
        DocumentReponseFactory = new DocumentResponseFactory(serializer);
    }

    public virtual async Task<DocumentResponse> GetAsync(GetDocumentRequest request) {
        using (var httpRequest = GetDocumentHttpRequestFactory.Create(request))
        {
            using (var httpResponse = await _cn.SendAsync(httpRequest).ForAwait())
            {
                return DocumentReponseFactory.Create(httpResponse);
            }
        }
    }
}

Bootstrapping

The default implementation of IMyCouchClient, MyCouchClient has a very simple constructor:

public MyCouchClient(ConnectionInfo connectionInfo, MyCouchClientBootstrapper bootstrapper = null)

The last and optional argument bootstrapper lets you pass a MyCouchClientBootstrapper. If you don't pass one, MyCouch will just create one using new MyCouchClientBootstrapper(). You could how-ever, extend MyCouchClientBootstrapper and pass in one of your own, or just create an instance of it and tweak it's public properties holding simple Func<> resolvers. NOTE! Please do have a look at the source code of it, so that you understand the scopes. E.g. always creating new EntityReflector would mess up some type caching and have negative effects on the performance.

Intercept requests and/or responses

There are two ways. The simplest would perhaps be, just registering an interceptor delegate against a client's connection:

client.Connection.BeforeSend = async request =>
{
    request.Headers.Add("X-Something", "Weird");
};

client.Connection.AfterSend = async response =>
{
    var s = await response.Content.ReadAsStringAsync();
    //...
};

The longer approach would be to create a custom connection. Please note there's also a ServerConnection:

public class MyDbConnection : DbConnection
{
    public MyDbConnection(ConnectionInfo connectionInfo)
        : base(connectionInfo) {}

    protected override void OnBeforeSend(HttpRequest httpRequest)
    {
        base.OnBeforeSend(httpRequest);
    }

    protected override void OnAfterSend(HttpResponseMessage httpResponse)
    {
        base.OnAfterSend(httpResponse);
    }
}

for use with a injected boostrapper:

var bs = new MyCouchClientBootstrapper
{
    DbConnectionFn = cnInfo => new MyDbConnection(cnInfo)
};
using (var client = new MyCouchClient("http://localhost:5984/", "testdb", bs))
{
    //... ...
}

Custom HTTP-Requests (IConnection)

If you find some operation that is not supported and you don't want to issue a pull request or wait for it to be implemented, you can always send arbitrary HTTP-requests using client.Connection.SendAsync. And then you can use e.g the (client.Serializer, client.Documents.Serializer) or (client.DocumentSerializer, client.Entities.Serializer) to work with data/content being passed with your custom requests or returned in the responses.

Documents vs Entities

The driver lets you work with pure JSON documents but it also lets you work with entities/POCOs. When using the native JSON API, you are responsible for ensuring the JSON contains required members, like: _id, _rev etc. When using the entity API, you get some help.

Entities

You don't have to extend any base-class or implement any interface but it's required that the class contains a string property for the _id and one for the _rev.

_id

The _id property could be named:

  • _id
  • [EntityName]Id
  • DocumentId
  • EntityId
  • Id

Please not that order is important. Hence _id has more precedence than Id.

A quick note about the values of id's. You are in charge of the _id value. If you don't provide one, CouchDb will assign one for you. Read more about this under Posting entities

Support for _id in the need of encoding

Since v2.4.2 this should work fluently for .Net4.5 & PCL but unfortunately not in a good way for .Net4.0. You can read more about it here: http://danielwertheim.se/uri-behaves-differently-in-net4-0-vs-net4-5/

NOTE, this only applies to .Net4.0, since the NuGet by Microsoft for supporting HttpClient in .Net4.0 uses the Uri that behaves differently than the one in .Net4.5, and that Microsoft hasn't adjusted for this (yet), you need to add Uri-configuration to e.g App.config. Again, ONLY FOR .NET4.0

<configuration>
  <uri>
    <schemeSettings>
      <add name="http" genericUriParserOptions="DontUnescapePathDotsAndSlashes"/>
    </schemeSettings>
  </uri>
</configuration>

_rev

The _rev property could be named:

  • _rev
  • [EntityName]Rev
  • DocumentRev
  • EntityRev
  • Rev

Please not that order is important. Hence _rev has more precedence than Rev.

This is something you can override, by injecting a custom implementation of IEntityMember and assign them to the Client.EntityReflector.

$doctype

When storing an entity (not applicable for anonymous types) via Client.Entities, the driver will automatically inject a property into the JSON document, called: $doctype. The value of it will be the value of the entity name (not full name). This is useful for filtering logic when you start designing your views and their map functions.

From v0.20.0 you can tweak this in different ways. Either via a simple class attribute: DocumentAttribute.DocType. You can also configure to inject a custom implementation of IDocumentSerializationMetaProvider (is injected to the DocumentSerializer in the MyCouchClientBootstrapper). It provides meta data about documents and entities for the serializers. The third option would be to hook in a custom IDocTypeSerializationConvention that lives in SerlalizationConfiguration.Conventions.

$docns

From v0.20.0 you can via a meta-data attribute DocumentAttribute specify a DocNamespace that will be injected in the JSON as $docns.

$docver

From v0.20.0 you can via a meta-data attribute DocumentAttribute specify a DocVersion that will be injected in the JSON as $docver. Useful if you want to version your data.

Posting Entities

If you insert an entity, e.g. using client.Entities.PostAsync and you don't have assigned a value to your _id property, CouchDb will generate a value for the document's _id property when stored. So if you have a model like this:

public class Artist {
    //Could be: _id | ArtistId | DocumentId | EntityId | Id
    public string ArtistId { get; set; }
   //... Other properties that you want to persist ...
}

If I would invoke client.Entities.PostAsync(myArtist) without assigning a value to ArtistId, the stored JSON will be something like this:

{
  "_id" : "5cfa4052cdfe40bb603057e75b000415",
  "_rev" : "1-8efb272ef3e4d956103ff859e3a320e8",
  "$doctype": "Artist",
  "ArtistId": null
}

When you pull the document the next time, in one way or the other, the deserialized entity will have the property ArtistId filled in with the value of _id and when you save it again, the JSON will have the same value for both _id as well as ArtistId.

But if you would have assigned a value upfront (you should probably use PUT instead), e.g. "SomeArtist1" and made a call to client.Entities.PostAsync(myArtist), the JSON would look something like this:

{
  "_id" : "SomeArtist1",
  "_rev" : "1-8efb272ef3e4d956103ff859e3a320e8",
  "$doctype": "Artist",
  "ArtistId": "SomeArtist1"
}

Independent of what strategy you choose, ensure to write your views against _id and not the property name of the property that holds the _id value in your entity.

Serialization

The serializer used (which you can switch) is the flexible Newtonsoft.Json serializer. If you want to use it directly, you can access it via: Client.Serializer, Client.Documents.Serializer or Client.Entities.Serializer. From v0.20.0 all these three serializer exists. The first one has no conventions, while the last one has most conventions, like injecting $doctype, extracting _id and _rev from many different naming conventions.

Using the Newtonsoft JSON.Net serializer, you could easily e.g. deserialize to dynamic types.

var getResponse = client.Documents.Get("someId");

var keyvalues = client.Serializer.Deserialize<IDictionary<string, dynamic>>(getResponse.Content);
keyvalues["year"] = 2000;

var doc = client.Serializer.Serialize(keyvalues);
var putResponse = client.Documents.Put(keyvalues["_id"], doc);

Entities and serialization

If you would use client.Documents nothing with the JSON will be touched when persisted. If you use the client.Entities API, there are conventions involved. You can of course customize the serializer configuration to what ever. BUT, even so, it will have it's conventions, e.g. picking the names of the properties as is defined in the class and camel-casing it: "FirstName" --> "firstName". Now, since not all drivers in the CouchDB echo system is driven by the same organization, there is never a guarantee that another driver would store this as "firstname". Could just as well be: "FIRSTNAME", "firstName", "FiRsTNaMe" or even auto aliasing it to reduce storage. So if your serializer in another project can not handle the persisted JSON due to casing, you might need to customize the deserialization configuration. In MyCouch, you need to hook in custom serialization configuration using the MyCouchClientBootstrapper. BUT MyCouch, can handle deserializing "FirstName, firstName, FiRsTnAmE, ...", but will always store as "firstName".

The result is a response

The client uses HTTP to communicate with CouchDb. So every command you invoke will generate a HTTP-request which will return a HTTP-response. What you will get in return is also a response. There are different kind of response: ViewQueryResponse, DocumentResponse, DocumentHeaderResponse ....

Lets look at an example:

var getDocumentResponse = client.Documents.GetAsync("someid");
//or
var getEntityResponse = client.Entities.GetAsync<Artist>("someid");

If you use a DEBUG compiled version of the driver, and invoke you invoke a ToString() on a response, you will see something like this:

{
    RequestUri: http://127.0.0.1:5984/test/1
    RequestMethod: GET
    Status: OK(200)
    IsSuccessful: true
    Error:<NULL>
    Reason: <NULL>
    ContentType: application/json
    Id: 1
    Rev: 34-e0813c9f99766efcf679468adb02c6ce
    Content: {"_id":"1","_rev":"34-e0813c9f99766efcf679468adb02c6ce","$doctype":"artist","name":"Fake artist 1","albums":[{"name":"Greatest fakes #1"}]}
}

You can also call response.ToStringDebugVersion() to get this dump.

So, with every invoke of a command you get a response having information about the request (URI etc); if it was successful or not; any error and reason; and if a document should be returned, it's located in the Content.

Views and queries

In CouchDb, you query views. You don't run ad-hoc queries, hence in the driver, you query your database using the Client.Views API or using MyCouchStore.Query | QueryAsync.

Create a model

Lets start with creating a model (you could work with plain JSON):

public class Artist
{
    //Could be _id, ArtistId, DocumentId, EntityId, Id
    public string ArtistId { get; set; }

    //Could be _rev, ArtistRev, DocumentRev, EntityRev, Rev
    public string ArtistRev { get; set; }

    public string Name { get; set; }

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

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

Create a view

Now let's define a simple view, that per artist returns it's albums:

{
    "_id": "_design/artists",
    "language": "javascript",
    "views": {
        "albums": {
            "map": "function(doc) {  if(doc.$doctype !== 'artist') return;  emit(doc.name, doc.albums);}" 
        }
    }
};

Since views are stored in a design document in CouchDb, you can insert/create them using the client:

client.Documents.Post(designDocumentAsJson);

QueryViewRequest

You query views via: Client.Views.Query<>(QueryViewRequest request).

By using a QueryViewRequest you get the possibility to perform the query against multiple databases. To do that, you would create the query once and then just execute it against whatever database(s) you want. Hence, the query is not attached to a certain database.

Configuring QueryViewRequest

This is done on the QueryViewRequest it self, using the Configure method. You could of course also set values directly on the request.

request.Configure(q => q
    .Stale(bool value)
    .IncludeDocs(bool value)
    .Descending(bool value)
    .Key(string value)
    .Keys(params string[] value)
    .StartKey(string value)
    .StartKeyDocId(string value)
    .EndKey(string value)
    .EndKeyDocId(string value)
    .InclusiveEnd(bool value)
    .Skip(int value)
    .Limit(int value)
    .Reduce(bool value)
    .UpdateSeq(bool value)
    .Group(bool value)
    .GroupLevel(int value));

ViewQueryResponse<>

All queries will return a ViewQueryResponse<>. It could take any form of the following:

  • ViewQueryResponse - Each row in response.Rows will be represented as a JSON-string.
  • ViewQueryResponse<string> - Each row in response.Rows will be represented as a JSON-string.
  • ViewQueryResponse<string[]> - Each row in response.Rows will be represented as a JSON-string array.
  • ViewQueryResponse<Album> - Each row in response.Rows will be represented as an entity.
  • ViewQueryResponse<Album[]> - Each row in response.Rows will be represented as an entity array.
  • ViewQueryResponse<dynamic> - Each row in response.Rows will be represented as an dynamic object.
  • ViewQueryResponse<int> - E.g. if you have a reduce that returns e.g. the sum of some mapped values.

If you configure the query to IncludeDocs you can also make use of the generic overload that lets you specify the type of the returned Value and the IncludedDoc.

The response also contains information about: TotalRowCount, Offset, RowCount etc.

Query for JSON-string

In this sample I'm running a query where I define the result as string. This means that the artist's albums will be represented as one JSON document, representing an array of albums.

var query = new QueryViewRequest("artists", "albums").Configure(query => query
    .Limit(5)
    .Reduce(false));

ViewQueryResponse result = await client.Views.QueryAsync(query);

or by specifying the return type as string:

var query = new QueryViewRequest("artists", "albums").Configure(query => query
    .Limit(5)
    .Reduce(false));

ViewQueryResponse<string> result = await client.Views.QueryAsync<string>(query);

Query for JSON-string[]

In this sample I'm running a query where I define the result as string[]. This means that the artist's albums will be represented as an array of JSON documents.

var query = new QueryViewRequest("artists", "albums").Configure(query => query
    .Limit(5)
    .Reduce(false));

ViewQueryResponse<string[]> result = await client.Views.QueryAsync<string[]>(query);

Query for Album[]

In this sample I'm running a query where I define the result as Album[]. This means that the artist's albums will be represented as an array of Album entities.

var query = new QueryViewRequest("artists", "albums").Configure(query => query
    .Limit(5)
    .Reduce(false));

ViewQueryResponse<Album[]> result = await client.Views.QueryAsync<Album[]>(query);

System views

If you want to consume one of the built-in CouchDb system views, use the QueryViewRequest constructor with one argument. Here's a sample of consuming the view all_docs:

var query = new QueryViewRequest("_all_docs");
var response = await client.Views.QueryAsync<dynamic>(query);

Lists

List functions are consumed by configuring queries e.g. using WithList(string name, [string accept]). By default, if no value is provided for accept, it will use application/json, text/html.

var query = new QueryViewRequest("mydesigndoc", "myview")
    .Configure(c=> c.WithList("mylist", "text/plain");

Show functions

Show functions are consumed via client.Documents.ShowAsync(QueryShowRequest). By default, if no value is provided for accept, it will use application/json, text/html.

var showRequest = new QueryShowRequest("mydesigndoc", "myshowfunc")
    .Configure(c => c.DocId("someDocId"));

var xmlShowRequest = new QueryShowRequest("mydesigndoc", "myshowfunc")
    .Configure(c => c.DocId("someDocId").Accepts(HttpContentTypes.Xml));

var response1 = await client.Documents.ShowAsync(showRequest);
var response2 = await client.Documents.ShowAsync(xmlShowRequest);

Attachments

From v0.7.0 there's simple support for explicitly working with attachments. This is exposed via client.Attachments. There's support for: PUT, GET and DELETE.

Attachment remarks

Please note, that currently, when pulling out a document that has an _attachments stub; you need to keep this intact. If you would persist it without this member, the attachments will not be associated with the document anymore.

PUT Attachment

If you don't provide a document revision (_rev), CouchDb will create the document for you. If you provide a specific _rev you will get an error if there's no document matching the sent _id and _rev.

var request = new PutAttachmentRequest(
    "myDocId",
    "myAttachmentName",
    HttpContentTypes.Text,
    bytes);

await client.Attachments.PutAsync(request);
var request = new PutAttachmentRequest(
    "myDocId",
    "a-valid-docRev",
    "myAttachmentName",
    HttpContentTypes.Text,
    bytes);

await client.Attachments.PutAsync(putCmd);

GET Attachment

You get the attachment using the document _id and attachmentName and optionally the document _rev.

AttachmentResponse response = await client.Attachments.GetAsync('docId', 'myAttachmentName');

or for a specific document revision:

AttachmentResponse response = await client.Attachments.GetAsync("myDocId", "a-valid-docRev", "myAttachmentName");

or using the GetAttachmentRequest:

GetAttachmentRequest request = new GetAttachmentRequest("myDocId", ["a-valid-docRev"], "myAttachmentName");
AttachmentResponse response = await client.Attachments.GetAsync(cmd);

You access the bytes for the attachment via response.Content.

Delete Attachment

You delete an attachment by specifying the document _id, _rev and the attachmentName:

DocumentHeaderResponse response = await client.Attachments.DeleteAsync("myDocId", "myDocRev", "myAttachmentName");

or using the DeleteAttachmentRequest:

DeleteAttachmentRequest request = new DeleteAttachmentRequest("myDocId", "myDocRev", "myAttachmentName");
DocumentHeaderResponse response = await client.Attachments.DeleteAsync(cmd);

Batch mode writes (POST, PUT)

As documented in the CouchDb docs: http://docs.couchdb.org/en/latest/api/database/common.html#batch-mode-writes. You can specify this by controlling the value of: PostDocumentRequest.Batch = true|false and PutDocumentRequest.Batch = true|false.

Bulk operations

MyCouch supports simple bulk operations. It works by using JSON representations of your documents. You use it like this:

var request = new BulkRequest()
    .Delete("someId", "someRev")
    .Delete("someId", "someRev")
    .Include(newJsonDoc1)
    .Include(newJsonDoc2)
    .Include(existingJsonDoc);

BulkResponse response = await client.Documents.BulkAsync(request);

Copy & Replace

From v0.4.0 you can use Client.Documents.Copy and Client.Documents.Replace to issue HTTP COPY requests supported by CouchDb.

Copy

DocumentHeaderResponse response = await client.Documents.CopyAsync(srcId, newId);

you can also copy a specific revision:

DocumentHeaderResponse response = await client.Documents.CopyAsync(srcId, srcRev, newId);

Replace

Replace is actually just a COPY with the difference that the target document already exists and you then have to specify a _rev for the target as well.

ReplaceDocumentResponse response = client.Documents.ReplaceAsync(srcId, trgId, trgRev);

you can also copy a specific revision:

ReplaceDocumentResponse response = client.Documents.ReplaceAsync(srcId, srcRev, trgId, trgRev);

Consume the Changes feed

From v0.15.0 MyCouch has a specific request builtin for supporting consuming data from the CouchDb Changes feed. It supports: Normal, LongPolling and Continuous.

Normal and Long polling

I will just show you the Normal request, since the one using long polling is issued in the exact same way, apart from that you have to specify Feed = ChangesFeed.Longpoll instead of ChangesFeed.Normal.

ChangesFeed.Normal - will perform the request and return the response immediately.

ChangesFeed.Longpoll- will perform the request and return the response the next time changes occur.

using (var client = new MyCouchClient("http://localhost:5984/samples"))
{
    //Specify the feed. If left out, the default one in CouchDb will be used
    //You can also specify Since, Heartbeat, IncludeDocs etc.
    var getChangesRequest = new GetChangesRequest { Feed = ChangesFeed.Normal };
    var changes = client.Changes.GetAsync(getChangesRequest).Result;
    
    //The resulting response contains a bunch of info, lets just pick sequence.
    foreach (var row in changes.Results)
        Console.WriteLine(row.Seq);
}

Continuous feed

The Continuous feed will keep the request open and continuously get data from the changes feed as it occurs. You consume it using the overload that takes an CancellationToken and also need to specify: Feed = ChangesFeed.Continuous. You can also specify a Heartbeat value so that it provides you with empty lines at every heartbeat so that you still know it is alive.

NOTE Since it's supposed to be a long-lived transaction, you need to adjust the Timeout for the client. This is easiest done by setting connectionInfo.Timeout = TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite);

var cnInfo = new ConnectionInfo("http://localhost:5984/samples")
{
    Timeout = TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite)
};
using (var client = new MyCouchClient(cnInfo))
{
    var getChangesRequest = new GetChangesRequest
    {
        Feed = ChangesFeed.Continuous,
        Since = 50, //Optional: FROM WHAT SEQUENCE DO WE WANT TO START CONSUMING
        Heartbeat = 3000 //Optional: LET COUCHDB SEND A I AM ALIVE BLANK ROW EACH ms
    };

    var cancellation = new CancellationTokenSource();
    client.Changes.GetAsync(
        getChangesRequest,
        data => Console.WriteLine(data),
        cancellation.Token);

    Console.ReadLine();

    //LETS CAUSE SOME CHANGES AND SEE THEM ARIVE ASYNCHRONOUSLY
    client.Documents.PostAsync("{""value"": 1}");
    client.Documents.PostAsync("{""value"": 2}");

    Console.ReadLine();

    cancellation.Cancel();
}

You can also perform the requests above by using client.Perform(GetChangesRequest request) and client.Perform(GetChangesRequest request, Action<string> onRead, CancellationToken token).

Caching

There is nothing built in to MyCouch when it comes to caching as this might be handled using proxy middleware etc. If you want, you could replace the inner connection that sends the actual requests. And in there determine for what entities and enpoints and verbs to cache for. Then use the CouchDB changes stream to invalidate the cache.

Conflicts

When using the client.Documents.GetAsync(request) you can configure the GetDocumentRequest.Conflicts = true so that it will include any conflict revs in the response. The same goes for GetEntityRequest<T>.

Machine specific connection strings

If you are part of a team (or running multiple developer machines) and want to have machine specific connection strings for e.g. running integration tests, you can make use of e.g. a simple helper to have different connection strings added in the config. Below is a sample that will use the current computer name and try to look up a connection string according to the format: cnstringname@machinename before falling back on cnstringname:

public static class ConnectionString
{
    public static string Get(string connectionStringName)
    {
        Ensure.That(connectionStringName, "connectionStringName").IsNotNullOrWhiteSpace();

        var machineSpecificName = string.Concat(connectionStringName, "@", Environment.MachineName);
        var config = ConfigurationManager.ConnectionStrings[machineSpecificName]
            ?? ConfigurationManager.ConnectionStrings[connectionStringName];

        return config == null
            ? connectionStringName
            : config.ConnectionString;
    }
}

This will allow you to have configuration like:

<connectionStrings>
  <add name="mycouchtests" connectionString="http://localhost:5984" />
  <add name="mycouchtests@mycomputer1" connectionString="http://foo:5984" />
  <add name="mycouchtests@mycomputer2" connectionString="http://bar:5984" />
</connectionStrings>

and be consumed like this:

var cnString = ConnectionString.Get("mycouchtests");

return new MyCouchClient(cnString);