- Notifications
You must be signed in to change notification settings - Fork31
EF Core-like CouchDB experience for .NET!
License
matteobortolazzo/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!
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors11
Uh oh!
There was an error while loading.Please reload this page.