EF Core-like CouchDB experience for .NET!
C# query example:
// Setup
public class MyDeathStarContext : CouchContext
{
    public CouchDatabase<Rebel> Rebels { get; set; }
    public CouchDatabase<Clone> Clones { get; set; }
    protected override void OnConfiguring(CouchOptionsBuilder optionsBuilder)
    {
      optionsBuilder
        .UseEndpoint("http://localhost:5984/")
        .EnsureDatabaseExists()
        .UseBasicAuthentication(username: "anakin", password: "empirerule");
    }
}
// Usage
await using var context = new MyDeathStarContext();
var skywalkers = await context.Rebels
    .Where(r => 
        r.Surname == "Skywalker" && 
        (
            r.Battles.All(b => b.Planet == "Naboo") ||
            r.Battles.Any(b => b.Planet == "Death Star")
        )
    )
    .OrderByDescending(r => r.Name)
    .ThenByDescending(r => r.Age)
    .Take(2)
    .Select(
        r => r.Name,
        r => r.Age
    })
    .ToListAsync();The produced Mango JSON:
{
  "selector": {
    "$and": [
      {
        "surname": "Skywalker"
      },
      {
        "$or": [
          {
            "battles": {
              "$allMatch": {
                "planet": "Naboo"
              }
            }
          },
          {
            "battles": {
              "$elemMatch": {
                "planet": "Death Star"
              }
            }
          }
        ]
      }
    ]
  },
  "sort": [
    { "name": "desc" },
    { "age": "desc" }
  ],
  "limit": 2,
  "fields": [
    "name",
    "age"
  ]
}- Getting started
- Queries
- Client operations
- Database operations
- Authentication
- Options
- Custom JSON values
- Attachments
- DB Changes Feed
- Indexing
- Database Splitting
- Views
- Local (non-replicating) Documents
- Bookmark and Execution stats
- Users
- Replication
- Dependency Injection
- Advanced
- Contributors
- Install it from NuGet: https://www.nuget.org/packages/CouchDB.NET
- Create a context or a client, where localhost will be the IP address and 5984 is CouchDB standard tcp port:
await using var context = new MyDeathStarContext(builder => {}); // or await using(var client = new CouchClient("http://localhost:5984", builder => {})) { } 
- Create a document class:
public class Rebel : CouchDocument 
- Get a database reference:
var rebels = context.Rebels; // or var rebels = client.GetDatabase<Rebel>(); 
- Query the database
var skywalkers = await rebels.Where(r => r.Surname == "Skywalker").ToListAsync(); 
The database class exposes all the implemented LINQ methods like Where and OrderBy, those methods returns an IQueryable.
LINQ are supported natively to the following is possible:
var skywalkers =
    from r in context.Rebels
    where r.Surname == "Skywalker"
    select r;The selector is created when the method Where (IQueryable) is called. If the Where method is not called in the expression, it will at an empty selector.
| Mango | C# | 
|---|---|
| $and | && | 
| $or | || | 
| $not | ! | 
| $nor | !( || ) | 
| $all | a.Contains(x) | 
| $all | a.Contains(list) | 
| $elemMatch | a.Any(condition) | 
| $allMatch | a.All(condition) | 
| Mango | C# | 
|---|---|
| $lt | < | 
| $lte | <= | 
| $eq (implicit) | == | 
| $ne | != | 
| $gte | >= | 
| $gt | > | 
| $exists | o.FieldExists(s) | 
| $type | o.IsCouchType(...) | 
| $in | o.In(list) | 
| $nin | !o.In(list) | 
| $size | a.Count == x | 
| $mod | n % x = y | 
| $regex | s.IsMatch(rx) | 
| Mango | C# | 
|---|---|
| limit | Take(n) | 
| skip | Skip(n) | 
| sort | OrderBy(..) | 
| sort | OrderBy(..).ThenBy() | 
| sort | OrderByDescending(..) | 
| sort | OrderByDescending(..).ThenByDescending() | 
| fields | Select(x => x.Prop1, x => x.Prop2) | 
| fields | Convert<SourceType, SimplerType>() | 
| use_index | UseIndex("design_document") | 
| use_index | UseIndex(new [] { "design_document", "index_name" }) | 
| r | WithReadQuorum(n) | 
| bookmark | UseBookmark(s) | 
| update | WithoutIndexUpdate() | 
| stable | FromStable() | 
| execution_stats | IncludeExecutionStats() | 
| conflicts | IncludeConflicts() | 
Some methods that are not directly supported by CouchDB are converted to a composition of supported ones!
| Input | Output | 
|---|---|
| Min(d => d.Property) | OrderBy(d => d.Property).Take(1).Select(d => d.Property).Min() | 
| Max(d => d.Property) | OrderByDescending(d => d.Property).Take(1).Select(d => d.Property).Max() | 
| Sum(d => d.Property) | Select(d => d.Property).Sum() | 
| Average(d => d.Property) | Select(d => d.Property).Average() | 
| Any() | Take(1).Any() | 
| Any(d => condition) | Where(d => condition).Take(1).Any() | 
| All(d => condition) | Where(d => !condition).Take(1).Any() | 
| Single() | Take(2).Single() | 
| SingleOrDefault() | Take(2).SingleOrDefault() | 
| Single(d => condition) | Where(d => condition).Take(2).Single() | 
| SingleOrDefault(d => condition) | Where(d => condition).Take(2).SingleOrDefault() | 
| First() | Take(1).First() | 
| FirstOrDefault() | Take(1).FirstOrDefault() | 
| First(d => condition) | Where(d => condition).Take(1).First() | 
| FirstOrDefault(d => condition) | Where(d => condition).Take(1).FirstOrDefault() | 
| Last() | Where(d => Last() | 
| LastOrDefault() | LastOrDefault() | 
| Last(d => condition) | Where(d => condition).Last() | 
| LastOrDefault(d => condition) | Where(d => condition).LastOrDefault() | 
INFO: Also Select(d => d.Property), Min and Max are supported.
WARN: Since Max and Min use sort, an index must be created.
Since v2.0 IQueryable methods that are not natively supported will throw an exception.
// CRUD from class name (rebels)
var rebels = client.GetDatabase<Rebel>();
var rebels = await client.GetOrCreateDatabaseAsync<Rebel>();
var rebels = await client.CreateDatabaseAsync<Rebel>();
await client.DeleteDatabaseAsync<Rebel>();
// CRUD specific name
var rebels = client.GetDatabase<Rebel>("naboo_rebels");
var rebels = client.GetOrCreateDatabaseAsync<Rebel>("naboo_rebels");
var rebels = await client.CreateDatabaseAsync<Rebel>("naboo_rebels");
await client.DeleteDatabaseAsync("naboo_rebels");
// Utils
var isRunning = await client.IsUpAsync();
var databases = await client.GetDatabasesNamesAsync();
var tasks = await client.GetActiveTasksAsync();// CRUD
await rebels.AddAsync(rebel);
await rebels.AddOrUpdateAsync(rebel);
await rebels.RemoveAsync(rebel);
var rebel = await rebels.FindAsync(id);
var rebel = await rebels.FindAsync(id, withConflicts: true);
var list = await rebels.FindManyAsync(ids);
var list = await rebels.QueryAsync(someMangoJson);
var list = await rebels.QueryAsync(someMangoObject);
// Bulk
await rebels.AddOrUpdateRangeAsync(moreRebels);
await rebels.DeleteRangeAsync(ids);
await rebels.DeleteRangeAsync(moreRebels);
// Utils
await rebels.CompactAsync();
var info = await rebels.GetInfoAsync();
// Security
await rebels.Security.SetInfoAsync(securityInfo);
var securityInfo = await rebels.Security.GetInfoAsync();// Basic
.UseBasicAuthentication("root", "relax")
// Cookie
.UseCookieAuthentication("root", "relax")
.UseCookieAuthentication("root", "relax", cookieDuration)
// Proxy
.UseProxyAuthentication("root", new[] { "role1", "role2" })
// JTW
.UseJwtAuthentication("token")
.UseJwtAuthentication(async () => await NewTokenAsync())The second parameter of the client constructor is a function to configure CouchSettings fluently.
public class MyDeathStarContext : CouchContext
{
  /* ... */
    protected override void OnConfiguring(CouchOptionsBuilder optionsBuilder)
    {
      optionsBuilder
        .UseBasicAuthentication("root", "relax")
        .DisableEntitisPluralization()
        ...
    }
}
// or
var client = new CouchClient("http://localhost:5984", builder => builder
    .UseBasicAuthentication("root", "relax")
    .DisableEntitisPluralization()
    ...
)| Method | Description | 
|---|---|
| UseBasicAuthentication | Enables basic authentication. | 
| UseCookieAuthentication | Enables cookie authentication. | 
| IgnoreCertificateValidation | Removes any SSL certificate validation. | 
| ConfigureCertificateValidation | Sets a custom SSL validation rule. | 
| DisableDocumentPluralization | Disables documents pluralization in requests. | 
| SetDocumentCase | Sets the format case for documents. | 
| SetPropertyCase | Sets the format case for properties. | 
| SetNullValueHandling | Sets how to handle null values. | 
| DisableLogOutOnDispose | Disables log out on client dispose. | 
- DocumentCaseTypes: None, UnderscoreCase (default), DashCase, KebabCase.
- PropertyCaseTypes: None, CamelCase (default), PascalCase, UnderscoreCase, DashCase, KebabCase.
If you need custom values for documents and properties, it's possible to use JsonObject and JsonProperty attributes.
[JsonObject("custom-rebels")]
public class OtherRebel : Rebel
[JsonProperty("rebel_bith_date")]
public DateTime BirthDate { get; set; }The driver fully support attachments, you can list, create, delete and download them.
// Get a document
var luke = new Rebel { Name = "Luke", Age = 19 };
// Add in memory
var pathToDocument = @".\luke.txt"
luke.Attachments.AddOrUpdate(pathToDocument, MediaTypeNames.Text.Plain);
// Delete in memory
luke.Attachments.Delete("yoda.txt");
// Save
luke = await rebels.CreateOrUpdateAsync(luke);
// Get
CouchAttachment lukeTxt = luke.Attachments["luke.txt"];
// Iterate
foreach (CouchAttachment attachment in luke.Attachments)
{ 
  ...
}
// Download
string downloadFilePath = await rebels.DownloadAttachment(attachment, downloadFolderPath, "luke-downloaded.txt");
//or
Stream responseStream = await rebels.DownloadAttachmentAsStreamAsync(attachment);The options for FindAsync(..) and AddOrUpdateAsync(..) support passing revision:
await _rebels.FindAsync("1", new FindOptions { Rev = "1-xxx" });
await _rebels.AddOrUpdateAsync(r, new AddOrUpdateOptions { Rev = "1-xxx" });For attachements revisions are supported by CouchAttachment class which is passing DocumentRev to DownloadAttachmentAsync(..) and DownloadAttachmentAsStreamAsync(..).
The following feed modes are supported: normal, longpool and continuous.
Also all options and filter types are supported.
Continuous mode is probably the most useful and it's implemented with the new IAsyncEnumerable.
var tokenSource = new CancellationTokenSource();
await foreach (var change in _rebels.GetContinuousChangesAsync(options: null, filter: null, tokenSource.Token))
{
    if (/* ... */) {
      tokenSource.Cancel();
    }
}// Example
var options = new ChangesFeedOptions
{
  Descending = true,
  Limit = 10,
  Since = "now",
  IncludeDocs = true
};
ChangesFeedResponse<Rebel> changes = await GetChangesAsync(options);// _doc_ids
var filter = ChangesFeedFilter.DocumentIds(new[] { "luke", "leia" });
// _selector
var filter = ChangesFeedFilter.Selector<Rebel>(rebel => rebel.Age == 19);
// _design
var filter = ChangesFeedFilter.Design();
// _view
var filter = ChangesFeedFilter.View(view);
// Use
ChangesFeedResponse<Rebel> changes = await GetChangesAsync(options: null, filter);It is possible to create indexes to use when querying.
// Basic index creation
await _rebels.CreateIndexAsync("rebels_index", b => b
    .IndexBy(r => r.Surname))
    .ThenBy(r => r.Name));
// Descending index creation
await _rebels.CreateIndexAsync("rebels_index", b => b
    .IndexByDescending(r => r.Surname))
    .ThenByDescending(r => r.Name));// Specifies the design document and/or whether a JSON index is partitioned or global
await _rebels.CreateIndexAsync("rebels_index", b => b
    .IndexBy(r => r.Surname),
    new IndexOptions()
    {
        DesignDocument = "surnames_ddoc",
        Partitioned = true
    });// Create an index which excludes documents at index time
await _rebels.CreateIndexAsync("skywalkers_index", b => b
    .IndexBy(r => r.Name)
    .Where(r => r.Surname == "Skywalker");// Get the list of indexes
var indexes = await _rebels.GetIndexesAsync();
// Delete an indexes
await _rebels.DeleteIndexAsync(indexes[0]);
// or
await _rebels.DeleteIndexAsync("surnames_ddoc", name: "surnames");Finally it's possible to configure indexes on the CouchContext.
public class MyDeathStarContext : CouchContext
{
    public CouchDatabase<Rebel> Rebels { get; set; }
    // OnConfiguring(CouchOptionsBuilder optionsBuilder) { ... }
    protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
    {
        databaseBuilder.Document<Rebel>()
            .HasIndex("rebel_surnames_index", b => b.IndexBy(b => b.Surname));
    }
}It is possible to use the same database for multiple types:
public class MyDeathStarContext : CouchContext
{
    public CouchDatabase<Rebel> Rebels { get; set; }
    public CouchDatabase<Vehicle> Vehicles { get; set; }
    // OnConfiguring(CouchOptionsBuilder optionsBuilder) { ... }
    protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
    {
        databaseBuilder.Document<Rebel>().ToDatabase("troops");
        databaseBuilder.Document<Vehicle>().ToDatabase("troops");
    }
}When multiple
CouchDatabasepoint to the same database, asplit_discriminatorfield is added on document creation.When querying, a filter by
split_discriminatoris added automatically.The field name can be overriden with the
WithDatabaseSplitDiscriminator.
If you are not using CouchContext, you can still use the database split feature:
var rebels = client.GetDatabase<Rebel>("troops", nameof(Rebel));
var vehicles = client.GetDatabase<Vehicle>("troops", nameof(Vehicle));It's possible to query a view with the following:
var options = new CouchViewOptions<string[]>
{
    StartKey = new[] {"Luke", "Skywalker"},
    IncludeDocs = true
};
var viewRows = await _rebels.GetViewAsync<string[], RebelView>("jedi", "by_name", options);
// OR
var details = await _rebels.GetDetailedViewAsync<int, BattleView>("battle", "by_name", options);You can also query a view with multiple options to get multiple results:
var lukeOptions = new CouchViewOptions<string[]>
{
    Key = new[] {"Luke", "Skywalker"},
    IncludeDocs = true
};
var yodaOptions = new CouchViewOptions<string[]>
{
    Key = new[] {"Yoda"},
    IncludeDocs = true
};
var queries = new[]
{
    lukeOptions,
    yodaOptions
};
var results = await _rebels.GetViewQueryAsync<string[], RebelView>("jedi", "by_name", queries);
var lukeRows = results[0];
var yodaRows = results[1];
// OR
var details = await _rebels.GetDetailedViewQueryAsync<string[], RebelView>("jedi", "by_name", queries);
var lukeDetails = details[0];
var yodaDetails = details[1];The Local (non-replicating) document interface allows you to create local documents that are not replicated to other databases.
var docId = "settings";
var settings = new RebelSettings
{
    Id = docId,
    IsActive = true
};
// Create
await _rebels.LocalDocuments.CreateOrUpdateAsync(settings);
// Get by ID
settings = await _rebels.LocalDocuments.GetAsync<RebelSettings>(docId);
// Get all
var docs = await local.GetAsync();
// Search
var searchOpt = new LocalDocumentsOptions
{
    Descending = true,
    Limit = 10,
    Conflicts = true
};
var docs = await local.GetAsync(searchOpt);If bookmark and execution stats must be retrieved, call ToCouchList or ToCouchListAsync.
var allRebels = await rebels.ToCouchListAsync();
foreach(var r in allRebels) 
{
    ...
}
var b = allRebels.Bookmark;
var ex = allRebels.ExecutionStats; // .IncludeExecutionStats() must be calledThe driver natively support the _users database.
var users = client.GetUsersDatabase();
var luke = await users.CreateAsync(new CouchUser(name: "luke", password: "lasersword"));It's possible to extend CouchUser for store custom info.
var users = client.GetUsersDatabase<CustomUser>();
var luke = await users.CreateAsync(new CustomUser(name: "luke", password: "lasersword"));To change password:
luke = await users.ChangeUserPassword(luke, "r2d2");The driver provides the ability to configure and cancel replication between databases.
if (await client.ReplicateAsync("anakin", "jedi", new CouchReplication() { Continuous = true}))
{
  await client.RemoveReplicationAsync("anakin", "jedi", new CouchReplication() { Continuous = true });
}It is also possible to specify a selector to apply to the replication
await client.ReplicateAsync("stormtroopers", "deathstar", new CouchReplication() { Continuous = true, Selector = new { designation = "FN-2187" } }));Credentials can be specified as follows
await client.ReplicateAsync("luke", "jedi", new CouchReplication() { SourceCredentials = new CouchReplicationBasicCredentials()username: "luke", password: "r2d2") }));As always you can leverage all the benefits of Dependency Injection.
Info: The context will be registered as a singleton.
- Create a CouchContextwith a constructor like the following:
public class MyDeathStarContext : CouchContext
{
    public CouchDatabase<Rebel> Rebels { get; set; }
    public MyDeathStarContext(CouchOptions<MyDeathStarContext> options)
        : base(options) { }
}- 
Register the context via any of supported containers (see appropriate section section below) 
- 
Inject the context: 
// RebelsController
public class RebelsController : Controller
{
    private readonly MyDeathStarContext _context;
    public RebelsController(MyDeathStarContext context)
    {
        _context = context;
    }
}- 
Install the DI package from NuGet: https://www.nuget.org/packages/CouchDB.NET.DependencyInjection 
- 
In the Startupclass register the context:
// ConfigureServices
services.AddCouchContext<MyDeathStarContext>(builder => builder
    .UseEndpoint("http://localhost:5984")
    .UseBasicAuthentication(username: "admin", password: "admin"));- 
Install the DI package from NuGet: https://www.nuget.org/packages/CouchDB.NET.DependencyInjection.Autofac 
- 
In the Startupclass register the context:
// ConfigureServices
var containerBuilder = new ContainerBuilder();
containerBuilder.AddCouchContext<MyDeathStarContext>(optionsBuilder => optionsBuilder
    .UseEndpoint("http://localhost:5984")
    .UseBasicAuthentication(username: "admin", password: "admin"));If requests have to be modified before each call, it's possible to override OnBeforeCallAsync.
protected virtual Task OnBeforeCallAsync(HttpCall call)Also, the configurator has ConfigureFlurlClient to set custom HTTP client options.
Ben Origas: Features, ideas and tests like SSL custom validation, multi queryable, async deadlock, cookie authentication and many others.
n9: Proxy authentication, some bug fixes, suggestions and the great feedback on the changes feed feature!
Marc: NullValueHandling, bug fixes and suggestions!
Panos: Help with Views and Table splitting.
Benjamin Höglinger-Stelzer, mwasson74, Emre ÇAĞLAR: Attachments improvements and fixes.
Dhiren Sham: Implementing replication.
Dmitriy Larionov: Revisions improvements.