- Notifications
You must be signed in to change notification settings - Fork0
EF Core-like CouchDB experience for .NET!
License
dhirensham/couchdb-net
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
EF Core-like CouchDB experience for .NET!
C# query example:
// SetuppublicclassMyDeathStarContext:CouchContext{publicCouchDatabase<Rebel>Rebels{get;set;}publicCouchDatabase<Clone>Clones{get;set;}protectedoverridevoidOnConfiguring(CouchOptionsBuilderoptionsBuilder){optionsBuilder.UseEndpoint("http://localhost:5984/").EnsureDatabaseExists().UseBasicAuthentication(username:"anakin",password:"empirerule");}}// Usageawaitusingvarcontext=newMyDeathStarContext();varskywalkers=awaitcontext.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:
awaitusingvarcontext=newMyDeathStarContext(builder=>{});// orawaitusing(varclient=newCouchClient("http://localhost:5984", builder=>{})){}
- Create a document class:
publicclassRebel:CouchDocument
- Get a database reference:
varrebels=context.Rebels;// orvarrebels=client.GetDatabase<Rebel>();
- Query the database
varskywalkers=awaitrebels.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:
varskywalkers=fromrincontext.Rebelswherer.Surname=="Skywalker"selectr;
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: AlsoSelect(d => d.Property)
,Min
andMax
are supported.
WARN: Since Max and Min usesort, anindex must be created.
Since v2.0IQueryable
methods that are not natively supported will throw an exception.
// CRUD from class name (rebels)varrebels=client.GetDatabase<Rebel>();varrebels=awaitclient.GetOrCreateDatabaseAsync<Rebel>();varrebels=awaitclient.CreateDatabaseAsync<Rebel>();awaitclient.DeleteDatabaseAsync<Rebel>();// CRUD specific namevarrebels=client.GetDatabase<Rebel>("naboo_rebels");varrebels=client.GetOrCreateDatabaseAsync<Rebel>("naboo_rebels");varrebels=awaitclient.CreateDatabaseAsync<Rebel>("naboo_rebels");awaitclient.DeleteDatabaseAsync("naboo_rebels");// UtilsvarisRunning=awaitclient.IsUpAsync();vardatabases=awaitclient.GetDatabasesNamesAsync();vartasks=awaitclient.GetActiveTasksAsync();
// CRUDawaitrebels.AddAsync(rebel);awaitrebels.AddOrUpdateAsync(rebel);awaitrebels.RemoveAsync(rebel);varrebel=awaitrebels.FindAsync(id);varrebel=awaitrebels.FindAsync(id,withConflicts:true);varlist=awaitrebels.FindManyAsync(ids);varlist=awaitrebels.QueryAsync(someMangoJson);varlist=awaitrebels.QueryAsync(someMangoObject);// Bulkawaitrebels.AddOrUpdateRangeAsync(moreRebels);awaitrebels.DeleteRangeAsync(ids);awaitrebels.DeleteRangeAsync(moreRebels);// Utilsawaitrebels.CompactAsync();varinfo=awaitrebels.GetInfoAsync();// Securityawaitrebels.Security.SetInfoAsync(securityInfo);varsecurityInfo=awaitrebels.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()=>awaitNewTokenAsync())
The second parameter of the client constructor is a function to configure CouchSettings fluently.
publicclassMyDeathStarContext:CouchContext{/* ... */protectedoverridevoidOnConfiguring(CouchOptionsBuilderoptionsBuilder){optionsBuilder.UseBasicAuthentication("root","relax").DisableEntitisPluralization() ...}}// orvarclient=newCouchClient("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")]publicclassOtherRebel: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 documentvarluke=newRebel{Name="Luke",Age=19};// Add in memoryvarpathToDocument=@".\luke.txt"luke.Attachments.AddOrUpdate(pathToDocument,MediaTypeNames.Text.Plain);// Delete in memoryluke.Attachments.Delete("yoda.txt");// Saveluke=awaitrebels.CreateOrUpdateAsync(luke);// GetCouchAttachmentlukeTxt=luke.Attachments["luke.txt"];// Iterateforeach(CouchAttachmentattachmentinluke.Attachments){ ...}// DownloadstringdownloadFilePath=awaitrebels.DownloadAttachment(attachment,downloadFolderPath,"luke-downloaded.txt");//orStreamresponseStream=awaitrebels.DownloadAttachmentAsStreamAsync(attachment);
The options forFindAsync(..)
andAddOrUpdateAsync(..)
support passing revision:
await_rebels.FindAsync("1",newFindOptions{Rev="1-xxx"});await_rebels.AddOrUpdateAsync(r,newAddOrUpdateOptions{Rev="1-xxx"});
For attachements revisions are supported byCouchAttachment
class which is passingDocumentRev
toDownloadAttachmentAsync(..)
andDownloadAttachmentAsStreamAsync(..)
.
The followingfeed modes are supported:normal
,longpool
andcontinuous
.Also alloptions andfilter types are supported.
Continuous mode
is probably the most useful and it's implemented with the newIAsyncEnumerable
.
vartokenSource=newCancellationTokenSource();awaitforeach(varchangein_rebels.GetContinuousChangesAsync(options:null,filter:null,tokenSource.Token)){if(/* ... */){tokenSource.Cancel();}}
// Examplevaroptions=newChangesFeedOptions{Descending=true,Limit=10,Since="now",IncludeDocs=true};ChangesFeedResponse<Rebel>changes=awaitGetChangesAsync(options);
// _doc_idsvarfilter=ChangesFeedFilter.DocumentIds(new[]{"luke","leia"});// _selectorvarfilter=ChangesFeedFilter.Selector<Rebel>(rebel=>rebel.Age==19);// _designvarfilter=ChangesFeedFilter.Design();// _viewvarfilter=ChangesFeedFilter.View(view);// UseChangesFeedResponse<Rebel>changes=awaitGetChangesAsync(options:null,filter);
It is possible to create indexes to use when querying.
// Basic index creationawait_rebels.CreateIndexAsync("rebels_index", b=>b.IndexBy(r=>r.Surname)).ThenBy(r=>r.Name));// Descending index creationawait_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 globalawait_rebels.CreateIndexAsync("rebels_index", b=>b.IndexBy(r=>r.Surname),newIndexOptions(){DesignDocument="surnames_ddoc",Partitioned=true});
// Create an index which excludes documents at index timeawait_rebels.CreateIndexAsync("skywalkers_index", b=>b.IndexBy(r=>r.Name).Where(r=>r.Surname=="Skywalker");
// Get the list of indexesvarindexes=await_rebels.GetIndexesAsync();// Delete an indexesawait_rebels.DeleteIndexAsync(indexes[0]);// orawait_rebels.DeleteIndexAsync("surnames_ddoc",name:"surnames");
Finally it's possible to configure indexes on theCouchContext
.
publicclassMyDeathStarContext:CouchContext{publicCouchDatabase<Rebel>Rebels{get;set;}// OnConfiguring(CouchOptionsBuilder optionsBuilder) { ... }protectedoverridevoidOnDatabaseCreating(CouchDatabaseBuilderdatabaseBuilder){databaseBuilder.Document<Rebel>().HasIndex("rebel_surnames_index", b=>b.IndexBy(b=>b.Surname));}}
It is possible to use the same database for multiple types:
publicclassMyDeathStarContext:CouchContext{publicCouchDatabase<Rebel>Rebels{get;set;}publicCouchDatabase<Vehicle>Vehicles{get;set;}// OnConfiguring(CouchOptionsBuilder optionsBuilder) { ... }protectedoverridevoidOnDatabaseCreating(CouchDatabaseBuilderdatabaseBuilder){databaseBuilder.Document<Rebel>().ToDatabase("troops");databaseBuilder.Document<Vehicle>().ToDatabase("troops");}}
When multiple
CouchDatabase
point to the samedatabase, asplit_discriminator
field is added on document creation.When querying, a filter by
split_discriminator
is added automatically.The field name can be overriden with the
WithDatabaseSplitDiscriminator
.
If you are not usingCouchContext
, you can still use the database split feature:
varrebels=client.GetDatabase<Rebel>("troops",nameof(Rebel));varvehicles=client.GetDatabase<Vehicle>("troops",nameof(Vehicle));
It's possible to query a view with the following:
varoptions=newCouchViewOptions<string[]>{StartKey=new[]{"Luke","Skywalker"},IncludeDocs=true};varviewRows=await_rebels.GetViewAsync<string[],RebelView>("jedi","by_name",options);// ORvardetails=await_rebels.GetDetailedViewAsync<int,BattleView>("battle","by_name",options);
You can also query a view with multiple options to get multiple results:
varlukeOptions=newCouchViewOptions<string[]>{Key=new[]{"Luke","Skywalker"},IncludeDocs=true};varyodaOptions=newCouchViewOptions<string[]>{Key=new[]{"Yoda"},IncludeDocs=true};varqueries=new[]{lukeOptions,yodaOptions};varresults=await_rebels.GetViewQueryAsync<string[],RebelView>("jedi","by_name",queries);varlukeRows=results[0];varyodaRows=results[1];// ORvardetails=await_rebels.GetDetailedViewQueryAsync<string[],RebelView>("jedi","by_name",queries);varlukeDetails=details[0];varyodaDetails=details[1];
The Local (non-replicating) document interface allows you to create local documents that are not replicated to other databases.
vardocId="settings";varsettings=newRebelSettings{Id=docId,IsActive=true};// Createawait_rebels.LocalDocuments.CreateOrUpdateAsync(settings);// Get by IDsettings=await_rebels.LocalDocuments.GetAsync<RebelSettings>(docId);// Get allvardocs=awaitlocal.GetAsync();// SearchvarsearchOpt=newLocalDocumentsOptions{Descending=true,Limit=10,Conflicts=true};vardocs=awaitlocal.GetAsync(searchOpt);
If bookmark and execution stats must be retrieved, callToCouchList orToCouchListAsync.
varallRebels=awaitrebels.ToCouchListAsync();foreach(varrinallRebels){ ...}varb=allRebels.Bookmark;varex=allRebels.ExecutionStats;// .IncludeExecutionStats() must be called
The driver natively support the_users database.
varusers=client.GetUsersDatabase();varluke=awaitusers.CreateAsync(newCouchUser(name:"luke",password:"lasersword"));
It's possible to extendCouchUser for store custom info.
varusers=client.GetUsersDatabase<CustomUser>();varluke=awaitusers.CreateAsync(newCustomUser(name:"luke",password:"lasersword"));
To change password:
luke=awaitusers.ChangeUserPassword(luke,"r2d2");
The driver provides the ability to configure and cancel replication between databases.
if(awaitclient.ReplicateAsync("anakin","jedi",newCouchReplication(){Continuous=true})){awaitclient.RemoveReplicationAsync("anakin","jedi",newCouchReplication(){Continuous=true});}
It is also possible to specify a selector to apply to the replication
awaitclient.ReplicateAsync("stormtroopers","deathstar",newCouchReplication(){Continuous=true,Selector=new{designation="FN-2187"}}));
Credentials can be specified as follows
awaitclient.ReplicateAsync("luke","jedi",newCouchReplication(){SourceCredentials=newCouchReplicationBasicCredentials()username:"luke",password:"r2d2")}));
As always you can leverage all the benefits of Dependency Injection.
Info: The context will be registered as asingleton
.
- Create a
CouchContext
with a constructor like the following:
publicclassMyDeathStarContext:CouchContext{publicCouchDatabase<Rebel>Rebels{get;set;}publicMyDeathStarContext(CouchOptions<MyDeathStarContext>options):base(options){}}
Register the context via any of supported containers (see appropriate section section below)
Inject the context:
// RebelsControllerpublicclassRebelsController:Controller{privatereadonlyMyDeathStarContext_context;publicRebelsController(MyDeathStarContextcontext){_context=context;}}
Install the DI package from NuGet:https://www.nuget.org/packages/CouchDB.NET.DependencyInjection
In the
Startup
class register the context:
// ConfigureServicesservices.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
Startup
class register the context:
// ConfigureServicesvarcontainerBuilder=newContainerBuilder();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.
protectedvirtualTaskOnBeforeCallAsync(HttpCallcall)
Also, the configurator hasConfigureFlurlClient
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.
About
EF Core-like CouchDB experience for .NET!
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Languages
- C#98.3%
- HTML1.5%
- Other0.2%