Released: Linq2Couchbase v1.1.0 the official Linq provider for Couchbase N1QL!

Earlier this week we released v1.1.0 (specifically v1.1.0.2) of Linq2Couchbase, the official Linq Provider for Couchbase Server and N1QL! This release includes new and experimental features as well as bug fixes and many, many improvements over 1.0.X. In this post we will discuss and demo the new features where applicable!

Contributors

Linq2Couchbase is a community-driven project, the number one contributer (thanks Brant!) is a community member! If you would like to contribute or send feedback, feel free to do so on the github project or the Jira project.

Major features and/or commits

More than 30 commits went into this release including the following features:

Compatibility with custom serializers by extending IExtendedTypeSerializer

Support for date functions such as DATE_DIFF, DATE_ADD, DATE_PART, and DATE_TRUNC

Support for Contains operations on arrays

Support for enumeration and GUID constants in queries

Support for UNION statements

Improved inline XML documentation

Support for asynchronous LINQ queries using .ExecuteAsync

Support for specifying query consistency level for RYOW

Improved error handling

Various bug fixes

In the next couple of paragraphs I’ll discuss a few of the more important/useful features.

Change Tracking and Proxying

A new “experimental” feature we have added to 1.1.0 is support for change tracking via proxy objects. The use-case for this feature is assume that you would like modify or add several documents, but you want to the mutation to happen in batch a later time. For example, you want your BucketContext object to have the lifespan of a Web request in ASP.NET: when the request begins, you want the context to be created, within any action methods you wish to make modifications or add documents and finally, when the request ends you want to submit everything back to the server…or roll back if an error occurred.

Change tracking is enabled by calling the BucketContext.EnableChangeTracking() method before initiating a query.

var db = new BucketContext(ClusterHelper.GetBucket("beer-sample")); db.BeginChangeTracking(); var query = from x in db.Query() where x.Type == "beer" select x; 1 2 3 4 5 6 7 var db = new BucketContext ( ClusterHelper . GetBucket ( "beer-sample" ) ) ; db . BeginChangeTracking ( ) ; var query = from x in db . Query ( ) where x . Type == "beer" select x ;

Once it’s called the BucketContext will intercept each row of the result set and create a dynamic proxy that will bubble up any changes to properties or the properties of children documents back to the BucketContext .

For example, assume we want to retrieve the first document from the list and modify the property Abv :

var beer = query.First(); beer.Abv = new decimal(12.6); 1 2 3 var beer = query . First ( ) ; beer . Abv = new decimal ( 12.6 ) ;

When the property Abv is set, it will trigger an event that will bubble all the way up to the BucketContext which will store a reference to the modified document. The actual update will not occur in Couchbase until later when SubmitChanges is called.

What about adding a new document? In that case you would just create your document as normal and then call Save on the BucketContext :

var newBeer = new Beer { Abv = 5, Category = "ale", Name = "Some Brew" }; db.Save(newBeer); 1 2 3 4 5 6 7 8 9 var newBeer = new Beer { Abv = 5 , Category = "ale" , Name = "Some Brew" } ; db . Save ( newBeer ) ;

The call to db.Save is required so that the new document (which is not a proxy) can be wrapped within a proxy and tracked. Finally when we want to push the changes back to the server, we call SubmitChanges :

db.SubmitChanges(); 1 2 db . SubmitChanges ( ) ;

Expect a much deeper example in a future post where we build an ASP.NET MVC application and show how the BucketContext can be used within an ASP.NEW Controller as a Unit of Work (UoW) scoped to the Web request.

Custom serializers via IExtendedTypeSerializer

For some time, the Couchbase .NET SDK has supported custom serializers. The default serializer is based on NewtonSoft JSON.NET which is a full featured, well supported serializer, however in some cases you may want to use another serializer like Jil or ServiceStack‘s Text. To support other JSON serializers other than NewtonSoft in Linq2Couchbase, we have added a new interface called IExtendedTypeSerializer. Additionally, the serialization mechanism has been extended to include QueryRequest as an integration point, meaning a custom serializer can be used on a per request basis as opposed to uniformly across the entire Couchbase .NET SDK.

N1QL supports many Date functions which do not translate directly to .NET DateTime methods. In this release we have added support for a number of these N1QL date functions including:

DATE_DIFF_STR

DATE_ADD_STR

DATE_PART_STR

DATE_TRUNC_STR

Also, a new enumeration was added called N1QLDatePart for the date part parameter accepted by each function. You can read more about working with dates here.

Relinq, the Open Source library that Linq2Couchbase (NHibernate and Entity Framework 7 as well) recently released 2.0, so we updated to the latest. In order to make it easier to use Linq2Couchbase with EF 7 within the same project and reduce the number of required dependencies, the Relinq (and Castle.Core) dependencies have been merged at build-time using ILRepack on the NuGet package.

Support for UNION and UNION ALL Statements

To combine the results of two more queries into a single result set, N1QL has two special statements: UNION and UNION ALL .

UNION returns the distinct results from each query and is now supported by Linq2Couchbase. For example, the following Linq query:

var query = db.Query(mockBucket.Object) .Where(e => e.Type == "beer") .Select(e => new { e.Name }) .Union( db.QueryBrewery>(mockBucket.Object) .Where(e => e.Type == "brewery") .Select(e => new { e.Name })); 1 2 3 4 5 6 7 8 var query = db . Query ( mockBucket . Object ) . Where ( e = > e . Type == "beer" ) . Select ( e = > new { e . Name } ) . Union ( db . QueryBrewery > ( mockBucket . Object ) . Where ( e = > e . Type == "brewery" ) . Select ( e = > new { e . Name } ) ) ;

Will be generated into a N1QL query that looks something like this:

SELECT `Extent1`.`name` as `Name` FROM `default` as `Extent1` WHERE (`Extent1`.`type` = 'beer') UNION SELECT `Extent2`.`name` as `Name` FROM `default` as `Extent2` WHERE (`Extent2`.`type` = 'brewery'); 1 2 3 4 SELECT ` Extent1 ` . ` name ` as ` Name ` FROM ` default ` as ` Extent1 ` WHERE ( ` Extent1 ` . ` type ` = 'beer' ) UNION SELECT ` Extent2 ` . ` name ` as ` Name ` FROM ` default ` as ` Extent2 ` WHERE ( ` Extent2 ` . ` type ` = 'brewery' ) ;

UNION ALL returns duplicates and the Linq syntax is slightly different in that we use the Concat method:

var query = db.Query(mockBucket.Object) .Where(e => e.Type == "beer") .Select(e => new { e.Name }) .Concat( db.Query(mockBucket.Object) .Where(e => e.Type == "brewery") .Select(e => new { e.Name })); 1 2 3 4 5 6 7 8 var query = db . Query ( mockBucket . Object ) . Where ( e = > e . Type == "beer" ) . Select ( e = > new { e . Name } ) . Concat ( db . Query ( mockBucket . Object ) . Where ( e = > e . Type == "brewery" ) . Select ( e = > new { e . Name } ) ) ;

The N1QL query emitted will look like this:

SELECT `Extent1`.`name` as `Name` FROM `default` as `Extent1` WHERE (`Extent1`.`type` = 'beer' UNION ALL SELECT `Extent2`.`name` as `Name` FROM `default` as `Extent2` WHERE (`Extent2`.`type` = 'brewery' 1 2 3 4 SELECT ` Extent1 ` . ` name ` as ` Name ` FROM ` default ` as ` Extent1 ` WHERE ( ` Extent1 ` . ` type ` = 'beer' UNION ALL SELECT ` Extent2 ` . ` name ` as ` Name ` FROM ` default ` as ` Extent2 ` WHERE ( ` Extent2 ` . ` type ` = 'brewery'

Note that multiple UNION s can be applied as well!

Asynchronous Linq Queries

Another cool feature that was added is support for asynchronous Linq queries using the async and await operators that are built into C#/.NET. Note support for both IEnumerable and scalar (Sum, First(), Any()) are supported.

Here is an example the returns an IEnumerable value:

var beers = from b in context.Query() select b; var results = (await beers.Take(1).ExecuteAsync()).ToList(); 1 2 3 4 5 var beers = from b in context . Query ( ) select b ; var results = ( await beers . Take ( 1 ) . ExecuteAsync ( ) ) . ToList ( ) ;

Here is another example demonstrating executing a scalar value:

var beers = from b in context.Query() select b; var result = await beers.ExecuteAsync(p => p.Average(q => q.Abv)); 1 2 3 4 5 var beers = from b in context . Query ( ) select b ; var result = await beers . ExecuteAsync ( p = > p . Average ( q = > q . Abv ) ) ;

In either case, the query request will executed in a non-blocking manner (ala thread pool). Cool stuff.

How to get it

You can get 1.1.0 Linq2Couchbase by either: