Generic Couchbase repository pattern in .Net Core 3.1

I am having one frustrating time attempting to follow the Couchbase documentation.

I already have several other persistence stacks following this same pattern. I would like to migrate to Couchbase from CosmosDb and I simply cannot figure out how to implement this in a generic pattern. FYI, I am not here to debate the move from Cosmos to Couchbase, so the let please leave that topic alone.

I have already done this for Mongo, Dynamo, PostresSql, MsftSql, Cosmos Document, Cosmos SQL, and Cosmos Table.

Nuget Packages:
Couchbase.Lite @ 2.7.1
CouchbaseNetClient @ 3.0.1

The IPlatformRepository interface, used by all repositories in the system

        Task<T> CreateAsync(T data);

        Task<T> ReadAsync(Guid id);

        Task<bool> UpdateAsync(T data);

        Task<bool> DeleteAsync(Guid id);

        Task<IEnumerable<T>> AllAsync();

        Task<IEnumerable<T>> QueryAsync(Expression<Func<T, bool>> predicate = null);

Here is the PoC code that I have in my Couchbase provider, but I am stuck at trying to figure out how to do the AllAsync() and QueryAsync() functions (commented out QueryAsync() is from the CosmosDocument provider I have. I have not started on that one yet. I started with the AllAsync() to just retrieve the documents.

    public class CouchbaseRepository<T> : IPlatformRepository<T>
        where T: DomainAggregate
    {
        private readonly string collectionName;
        private readonly CouchbaseSettings couchbaseSettings;
        private ICluster cluster;
        private IBucket bucket;
        private ICollection<T> collection;
        private ICouchbaseCollection cbc;

        public CouchbaseRepository(CouchbaseSettings settings)
        {
            Inflector.Inflector.SetDefaultCultureFunc = () => new CultureInfo("en");
            collectionName = typeof(T).Name.Pluralize().Uncapitalize();
        }
        
        public async Task<T> ReadAsync(Guid id)
        {
            var result = await bucket.Collection(collectionName).GetAsync(id.ToString());
            return result.ContentAs<T>();
        }

        public async Task<IEnumerable<T>> QueryAsync(Expression<Func<T, bool>> predicate)
        {
            // var results = new List<T>();
            //
            // var query = client.CreateDocumentQuery<T>(
            //     UriFactory.CreateDocumentCollectionUri(
            //         databaseId,
            //         collectionId),
            //     new FeedOptions
            //     {
            //         MaxItemCount = -1,
            //         EnableCrossPartitionQuery = true
            //     });
            //
            // predicate = null;
            // var conditionalQuery = predicate == null
            //     ? query
            //     : query.Where(predicate);
            //
            // var documentQuery = conditionalQuery.AsDocumentQuery();
            //
            // while (documentQuery.HasMoreResults)
            // {
            //     results.AddRange(await documentQuery.ExecuteNextAsync<T>());
            // }
            //
            // return results;
        }

        public async Task<IEnumerable<T>> AllAsync()
        {
            using (var cluseter = new Cluster())
            {
                
            }
        }

        public async Task<T> CreateAsync(T item)
        {
            var result = await bucket.Collection(collectionName).InsertAsync(item.Id.ToString(), item);
            return item;
        }

        public async Task<bool> UpdateAsync(T item)
        {
        }

        public async Task<bool> DeleteAsync(Guid id)
        {
        }

        private async Task EstablishConnection()
        {
            cluster = await Cluster.ConnectAsync(couchbaseSettings.Endpoint, couchbaseSettings.User, couchbaseSettings.Password);
            bucket = await cluster.BucketAsync(couchbaseSettings.Bucket);
        }
    }

@meesterover

A couple of points for you. First of all, the cluster should probably be scoped to the application lifetime, rather than to your repository. There is a significant performance penalty involved in bootstrapping a cluster/bucket that you definitely want to only encounter on startup.

Second, there is no native support for LINQ queries in the SDK. For that, you’ll want to use the Linq2Couchbase package. This package is owned and managed by Couchbase, but is not officially supported. However, we
use it extensively at my company.

Unfortunately, this package has not yet been updated for SDK 3 compatibility. That work is in progress. The 3.0 SDK was a major refactor, so there is a lot of work to be done.

1 Like

Thank you for the tips.

On the first point, I will look into scoping it at the application level.

I will have to wait for something that allows for a more generic implementation before I consider switching to Couchbase then.

Hi @meesterover, Is it the separate package or lack of “official support” that concerns you?

@jmorris

It is neither actually. I am just not able to figure out how to write the generic repository with Couchbase as the provider. As an example, from my domain handler, I have this code that performs a query for a food.

        var results = await foodRepository
            .QueryAsync(x =>
                x.Description.Contains(foodQuery.Description) &&
                x.Name.Contains(foodQuery.Name));

I cannot figure out how to write the QueryAsync function for my base Couchbase repository

Hi @meesterover -

The Couchbase SDK itself supports only raw queries; linq2couchbase and in the near future, Couchbase EF, will support LInq queries for N1QL. In this case you would have to have to translate the expression to N1QL. There is already work being started for Linq2Couchbase v2 that will support Couchbase SDK3.

If you need this now, I would start with Couchbase .NET SDK2 and then migrate to sdk3 when the functionality is available.

-Jeff

Thanks for the info. I will not use Couchbase.