Giter Site home page Giter Site logo

matteobortolazzo / couchdb-net Goto Github PK

View Code? Open in Web Editor NEW
123.0 13.0 30.0 1.51 MB

EF Core-like CouchDB experience for .NET!

License: MIT License

C# 98.21% HTML 1.54% CSS 0.21% JavaScript 0.04%
couchdb driver nosql database netstandard xamarin pouchdb efcore netcore aspnetcore

couchdb-net's Introduction

Downloads

CouchDB.NET

EF Core-like CouchDB experience for .NET!

LINQ queries

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"
  ]
}

Index

Getting started

  • 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();

Queries

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;

Selector

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.

Combinations

Mango C#
$and &&
$or ||
$not !
$nor !( || )
$all a.Contains(x)
$all a.Contains(list)
$elemMatch a.Any(condition)
$allMatch a.All(condition)

Conditions

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)

Native methods

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()

Composite methods

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.

All other IQueryables methods

Since v2.0 IQueryable methods that are not natively supported will throw an exception.

Client operations

// 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();

Database operations

// 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();

Authentication

// 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())

Options

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.

Custom JSON values

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; }

Attachments

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);

Revisions

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(..).

DB Changes Feed

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();
    }
}

Feed Options

// Example
var options = new ChangesFeedOptions
{
  Descending = true,
  Limit = 10,
  Since = "now",
  IncludeDocs = true
};
ChangesFeedResponse<Rebel> changes = await GetChangesAsync(options);

Feed Filter

// _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);

Indexing

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));

Index Options

// 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
    });

Partial Indexes

// 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");

Indexes operations

// 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");

CouchContext Index Configuration

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));
    }
}

Database Splitting

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 CouchDatabase point to the same database, a split_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 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));

Views

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];

Local (non-replicating) Documents

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);

Bookmark and Execution stats

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 called

Users

The 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");

Replication

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") }));

Dependency Injection

As always you can leverage all the benefits of Dependency Injection.

Info: The context will be registered as a singleton.

  • Create a CouchContext with 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;
    }
}

Microsoft container

// ConfigureServices
services.AddCouchContext<MyDeathStarContext>(builder => builder
    .UseEndpoint("http://localhost:5984")
    .UseBasicAuthentication(username: "admin", password: "admin"));

Autofac container

// ConfigureServices
var containerBuilder = new ContainerBuilder();
containerBuilder.AddCouchContext<MyDeathStarContext>(optionsBuilder => optionsBuilder
    .UseEndpoint("http://localhost:5984")
    .UseBasicAuthentication(username: "admin", password: "admin"));

Advanced

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.

Contributors

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.

couchdb-net's People

Contributors

adcorduneanu avatar alexandrshad avatar biapar avatar borigas avatar dhirensham avatar dmlarionov avatar matteobortolazzo avatar nefarius avatar panoukos41 avatar sacrilege avatar tjrobinson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

couchdb-net's Issues

Camelized Member Names

@matteobortolazzo Why camelize the member names in

return jsonProperty != null ? jsonProperty.PropertyName : memberInfo.Name.Camelize();

In my case, the data in couch is all pascal case, so queries no longer find anything. I'm going to work around it locally by removing that, but it feels intentional, so I don't think I should PR without discussion. It feels like at least an option is necessary here

Discriminator field can't be used

Hey, I was looking at the split feature instructions and noticed that the discriminator is set to be serialized as _discriminator but CouchDB will not allow that as shown in the picture below because _ character is considered special

image

Replacing LastExecutionStats and LastBookmark.

@borigas
Any idea on how to get ExecutionStats and Bookmark?
The current implementation doesn't guarantee that the "Last" is really the last if async is involved.

Other than that and NewIndex, there should be all the previous features.

IsUpAsync throws uncaught CouchException instead of returning success value

Expected:
CouchClient's IsUpAsync method returns false if connection can't be made / service is not up

Actual:
Uncaught CouchException is thrown

Tried to check whether valid CouchClient connection is up and running by using IsUpAsync method, but received uncaught CouchException instead when invalid IP/port was specified. I would expect it to return false when connection can't be established.

I did check from source code that RequestsHelper's SendRequestAsync method throws CouchNotFoundException only if HTTP 404 status is returned. However. If service is not up it might return other status codes or not respond at all.

In IsUpAsync method it's only catching CouchNotFoundException exception which may be too specific. Should it catch CouchException instead to cover up these other cases, or is there something else that could be done?

As a workaround I am currently catching this exception my self.

Query fails due to absence of Index

I must admit being a newbie for CouchDB. While trying to use where predicates with query APIs, an exception is thrown indicating no Index found for the KeySpace... In this regard, I have below questions -

  1. How to create the index from the SDK? I couldn't find any related API in the SDK.
  2. Is it required to have a index created for each attribute that is required for filtering ?
  3. How does it work to know (and thereby fixing) the model schema upfront in a NoSql world? Aren't document / model supposed to be generic and not known upfront? What are the best practices here?

IsMatch

IsMatch is moved to internal and cant be used
how is the like search now?

image

Version 1.2.2. this works
var result = db.Where(x => x.SearchText.IsMatch(match));

AddOrUpdateAsync throws "conflict" exception

What I expect:

Adding or Updating a Document should either add or update a document, without throwing a "conflict" exception

The Code

  public async Task<TReadModel> AddOrUpdateAsync(TReadModel entity)
        {
            var newdoc = CreateCDoc(entity);
            var doc = CreateCDoc(entity);
            try
            {
                 doc = await Db.AddOrUpdateAsync(newdoc);
            }
            catch (Exception e)
            {
                _logger?.Fatal( JsonConvert.SerializeObject(e.ToXeption()));
                doc = await Db.FindAsync(entity.Id);
                if (doc == null)
                {
                    doc = await Db.AddAsync(newdoc);
                }
                else
                {
                    await Db.RemoveAsync(doc);
                    doc = await Db.AddAsync(newdoc);
                }
            }
            return doc.Data;
        }

The Exception:

 {"Subject":"Unexpected Error","Content":"conflict","Msg":null,"Stack":"   at CouchDB.Driver.Helpers.RequestsHelper.SendRequestAsync[TResult](Task`1 asyncRequest)\n   at CouchDB.Driver.CouchDatabase`1.AddOrUpdateAsync(TSource document, Boolean batch, CancellationToken cancellationToken)\n   at M5x.Store.CouchStore`1.AddOrUpdateAsync(TReadModel entity) in /home/rl/work/macula.io/netcore/m5x-sdk/src/M5x.Store/CouchStore.cs:line 70","Source":"CouchDB.NET","StatusCode":0,"Server":null,"ProtocolVersion":null,"Method":null,"LastModified":"2020-10-25T20:57:27.9994507Z","StatusDescription":null,"ErrorMessage":"conflict","ResponseStatus":null,"InnerXeption":{"Subject":"Unexpected Error","Content":"Call failed with status code 409 (Conflict): PUT http://toor:dev@localhost:5984/parksim-game/88609cbb122e4cfc9e476373520ce1c7","Msg":null,"Stack":"   at Flurl.Http.FlurlRequest.HandleExceptionAsync(HttpCall call, Exception ex, CancellationToken token)\n   at Flurl.Http.FlurlRequest.SendAsync(HttpMethod verb, HttpContent content, CancellationToken cancellationToken, HttpCompletionOption completionOption)\n   at Flurl.Http.FlurlRequest.SendAsync(HttpMethod verb, HttpContent content, CancellationToken cancellationToken, HttpCompletionOption completionOption)\n   at Flurl.Http.HttpResponseMessageExtensions.ReceiveJson[T](Task`1 response)\n   at CouchDB.Driver.Helpers.RequestsHelper.SendRequestAsync[TResult](Task`1 asyncRequest)","Source":"Flurl.Http","StatusCode":0,"Server":null,"ProtocolVersion":null,"Method":null,"LastModified":"2020-10-25T20:57:28.0113833Z","StatusDescription":null,"ErrorMessage":"Call failed with status code 409 (Conflict): PUT http://toor:dev@localhost:5984/parksim-game/88609cbb122e4cfc9e476373520ce1c7","ResponseStatus":null,"InnerXeption":null}

Query Optimizer doesn't handle short circuited logical expressions

It looks like the query optimizer could be improved to handle short circuited logical And/Or expressions. Currently the optimizer/translator will generate invalid mango queries.

Example:

bool useFilter = true; //from user input...
var mango = _database.Where(doc => useFilter && doc.MyField == "foobar").ToString();

The query generated is
{
"$and" : [
true,
"$eq": {
"myfield":"foobar"
}
]
}

I would have expected the optimizer to 'prune' all the 'true/false' branches for And/Or. Instead, the above query returns nothing.

The workaround is to do this:
if (useFilter) //use constant outside of expression
{
_database.Where(...);
}

It works, but it's not intuitive to the user.

wrong type for purge_seq in CouchDatabaseInfo

purge_seq is string not int in CouchDB/3.1.0

CouchDB.Driver.Exceptions.CouchException : Exception of type 'CouchDB.Driver.Exceptions.CouchException' was thrown. ---- Flurl.Http.FlurlParsingException : Response could not be deserialized to JSON: GET http://localhost:5984/xunit-complete -------- Newtonsoft.Json.JsonReaderException : Could not convert string to integer: 0-g1AAAABPeJzLYWBgYMpgTmHgzcvPy09JdcjLz8gvLskBCeexAEmGBiD1HwiyEhlwqEtkSKqHKMgCAIT2GV4. Path 'purge_seq', line 1, position 127. at CouchDB.Driver.Helpers.RequestsHelper.SendRequestAsync[T](Task1 asyncRequest)
at CouchDB.Driver.CouchDatabase1.GetInfoAsync() at LogetherFleet.CouchDb.Tests.CockpitOrderServiceTests.CountTest() in C:\_aaaaaa\logether-fleet\src\backend\CouchDbTests\CockpitOrderServiceTests.cs:line 131 --- End of stack trace from previous location where exception was thrown --- ----- Inner Stack Trace ----- at Flurl.Http.FlurlRequest.HandleExceptionAsync(HttpCall call, Exception ex, CancellationToken token) at Flurl.Http.HttpResponseMessageExtensions.ReceiveJson[T](Task1 response)
at CouchDB.Driver.Helpers.RequestsHelper.SendRequestAsync[T](Task1 asyncRequest) ----- Inner Stack Trace ----- at Newtonsoft.Json.JsonReader.ReadInt32String(String s) at Newtonsoft.Json.JsonTextReader.FinishReadQuotedNumber(ReadType readType) at Newtonsoft.Json.JsonTextReader.ReadAsInt32() at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize[T](JsonReader reader) at Flurl.Http.Configuration.NewtonsoftJsonSerializer.Deserialize[T](Stream stream) at Flurl.Http.HttpResponseMessageExtensions.ReceiveJson[T](Task1 response)`

`GET http://localhost:5984/xunit-complete

HTTP/1.1 200 OK
Cache-Control: must-revalidate
Content-Length: 444
Content-Type: application/json
Date: Thu, 02 Jul 2020 09:14:12 GMT
Server: CouchDB/3.1.0 (Erlang OTP/20)
X-Couch-Request-ID: a1581a3ed2
X-CouchDB-Body-Time: 0

{
"db_name": "xunit-complete",
"purge_seq": "0-g1AAAABPeJzLYWBgYMpgTmHgzcvPy09JdcjLz8gvLskBCeexAEmGBiD1HwiyEhlwqEtkSKqHKMgCAIT2GV4",
"update_seq": "78-g1AAAABPeJzLYWBgYMpgTmHgzcvPy09JdcjLz8gvLskBCeexAEmGBiD1HwiyErVwqEtkSKoHK1DJAgCK_Bms",
"sizes": {
"file": 536986,
"external": 264719,
"active": 227487
},
"props": {},
"doc_del_count": 0,
"doc_count": 50,
"disk_format_version": 8,
"compact_running": false,
"cluster": {
"q": 2,
"n": 1,
"w": 1,
"r": 1
},
"instance_start_time": "0"
}`

Replication & updates

Are you planning to implement replication and update events? I am interested in using it.

One database with multiple document models?

It appears that the CouchDatabase database name is the name of the CouchDocument class and, thus, one can only have one CouchDocument class per database. If this is correct, I propose the addition of a means to associate multiple document models with a specific database. Perhaps an over name in AddCouchContext or an attributes.

Thank you! CoachDB.Net is great. Well done.

Expose CouchClient.NewRequest

Making CouchClient.NewRequest public would easily allow creating custom requests. (It might not be available on ICouchClient interface.)

EnsureDatabaseExists

  1. GetDatabase with CheckDatabaseExists setting can create database, if it does not exist. However, it is done synchronously (via AsyncContext.Run). What about to provide another method ObtainDatabaseAsync?

  2. GetDatabasesNamesAsync is called to check whether database exists. What about to use HEAD /db, which is more lightweight? (Moreover, what about to provide that functionality in function DatabaseExistsAsync?)

  3. It seems to me that in case that database does not exist, its name is escaped twice: first in GetDatabase and then again in CreateDatabaseAsync.

Unable to create queries from variables

After updating from 1.2.1 to latest 2.0.2 version conditions from variables stopped working. This same issue seems to happen in 2.0 version also.

I am able to call the Where method with variables in expressions without exceptions, but it returns with invalid results.

I would expect it to return the documents that matches with variables values, but it returns 0 documents instead. This worked like I would expect in earlier 1.2.1 version.

Running condition with constants in 2.0 / 2.0.2 version works as expected; it returns the documents that match with given condition.

It seems that the Where clause generates proper mango query that works as expected when run from CouchDb web administrative control panel. The same mango query doesn't get sent with CouchClient although. It is sending parameter name instead of its value

CouchDB testdb database document:

{
	"id":"someid",
	"property1" : "a"
}

Not working C# POC Code:

var database = GetDatabase<TestDocument>("testdb");
var variable1 = "a";
var mangoQuery = database.Where(d => d.Property1 == variable1);
var documentResults = mangoQuery.ToList();

Working C# POC Code:

var database = GetDatabase<TestDocument>("testdb");
var mangoQuery = database.Where(d => d.Property1 == "a");
var documentResults = mangoQuery.ToList();

Where clause generated mango query:

   "selector": {
      "$and": [
         {
            "property1": "a"
         }
      ]
   }

Actual what is being sent (checked from OnBeforeCallAsync httpCall param):

   "selector": {
      "$and": [
         {
            "property1": "variable1"
         }
      ]
   }

Expected:

   "selector": {
      "$and": [
         {
            "property1": "a"
         }
      ]
   }

EDIT1:
Checked that the call on
OnBeforeCallAsync
doesn't match the generated mango query. Seems that my example was little different. I'll update this soon

EDIT2:
Seems it was not about multi query after all. Updated the question, apologies for not noticing this earlier.

EDIT3:
Updated question + title

Documentation create index

the documentation says that we need an index for min/max

WARN: Since Max and Min use sort, an index must be created.

how do you do it?

Unable to update the document

  `public async Task<IEnumerable<Voyage>> SetEmailSent(IEnumerable<Voyage> voyages)
    {
        if (voyages == null || !voyages.Any())
            return null;

        using (var couchClient = new CouchClient(couchDbUrl, g => g.EnsureDatabaseExists()))
        {
            var voyageMaster = couchClient.GetDatabase<Voyage>("test");

            foreach (var voyage in voyages)
            {
                voyage.isEmailed = true;

            }

            return await voyageMaster.CreateOrUpdateRangeAsync(voyages);
        }
    }`

I'm getting an couchdb conflict response.
I'm reading from a db which have two records which have created on same time stamp. i believe when getting the data i should include IncludeConflicts() but making it throw a Sequence contains no element error

I have these issue on 1.1.4 and i have updated 1.2.0.0 still the issue persis.
Let me know how this can be fixed.

Exception
Document update conflict.

Stacktrace

at CouchDB.Driver.Extensions.CouchDocumentExtensions.ProcessSaveResponse(CouchDocument item, DocumentSaveResponse response)
   at CouchDB.Driver.CouchDatabase`1.<CreateOrUpdateRangeAsync>d__39.MoveNext()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

I have recently added index to the db is this reason for throwing this exception

Not getting all documents when using ToList/ToListAsync

i'm trying to query all documents from services database

await using(var db = new DatabaseContext())
            {
                var services = await db.Services.ToListAsync();
            .
            .
            .
            }

the services database has 29 documents
services

but the statement returns only 25 records, exactly the first 25 records, and the last 4 are ignored, i've tried to add 1 more record, and the same happened, the first 25 records are returned and 5 got ignored.

is there any configuration to get all data??

Custom serializer and multiple models

Hi!

I use couch db database with multiple models from F#, I was wondering:

  1. How to add work with a different serializer (because F# serialized bit weird)?
  2. How to work with different documents in the same db?

I assume I can use diff "databases" that are the same but with different TSource.:

var rebels = client.GetDatabase<Rebel>("StarWars");
var clones = client.GetDatabase<Clone>("StarWars");

$in queries with enums are serialized as names, not ints

[Fact]
public void Array_InEnum()
{
     var json = _rebels.Where(r => r.Species.In(new[] { Species.Human, Species.Droid })).ToString();
     Assert.Equal(@"{""selector"":{""species"":{""$in"":[0,1]}}}", json);
}

fails. It gives {"selector":{"species":{"$in":[Human,Droid]}}}, which doesn't match other tests (Find_Selector.Enum()) that are expecting enums to be serialized to ints

FindManyAsync crashes with NRE

FindManyAsync crashes when passing non-existent document id.

  NullReferenceException: Object reference not set to an instance of an object.
  at CouchDB.Driver.CouchDatabase`1.InitAttachments(TSource document)
  at CouchDB.Driver.CouchDatabase`1.FindManyAsync(IEnumerable`1 docIds)

FindAsync without catch

FindAsync currently catches CouchNotFoundException so it returns null in case that document is not found. I have mocked an implementation that is not using catch: n9@a877807. In case you would like me to pull-request it, let me know.

Question, update user password

Currentlly we can create user, its works fine.
How i can update the password? Its not documented or not implemented yet?

ICouchDatabase.AddOrUpdate

Not sure about your conventions, but ICouchDatabase<T>.AddOrUpdate does not have Async suffix, so it is not consistent with other methods in the ICouchDatabase<T>.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.