A newer version of this software is available
You are viewing the documentation for an older version of this software. To find the documentation for the current version, visit the Couchbase documentation home page.
This guide provides information for developers who want to use the Couchbase .NET SDK to build applications that use Couchbase Server.
Beginning with the 1.2.5 release, Couchbase .NET Client Library supports .NET Framework versions 3.5 and 4.0.
This section helps you get started using Couchbase Server and the .NET (C#) Client Library.
To get the software:
Get the client library by using one of the following methods:
Create a new console project in Visual Studio. Add references to the Couchbase.dll, Enyim.Caching.dll, and Newtonsoft.Json.dll assemblies that are in the release zip file.
Visual Studio console applications target the .NET Framework Client Profile by default, so you need to change the project properties to target the full .NET Framework. If you skip this step, you’ll have compilation errors.
You can configure your Couchbase client either programmatically or using the app.config file with the appropriate Couchbase configuration section. Using app.config s more flexible and is the preferred approach. Modify your app.config file as follows:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="couchbase" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase"/>
</configSections>
<couchbase>
<servers bucket="default" bucketPassword="">
<add uri="http://192.168.0.2:8091/pools"/>
<add uri="http://192.168.0.3:8091/pools"/>
</servers>
</couchbase>
</configuration>
The URIs in the servers list are used by the client to obtain information about the cluster configuration. If you’re running on your local development machine, include only a single URI and use 127.0.0.1 as the address.
The bucket
and bucketPassword
attributes are optional because the default Couchbase Server installation creates a bucket named default
that does not use a password. If you create an authenticated bucket, you must specify those values in place of the default settings shown in the example.
By default, the TCP/IP port allocation on Windows includes a restricted number of ports available for client communication. For more information on this issue, including information about how to adjust the configuration and increase the available ports, see MSDN: Avoiding TCP/IP Port Exhaustion.
To instantiate the client, add the following using statements to the Program.cs file:
using Couchbase;
using Enyim.Caching.Memcached;
using Newtonsoft.Json;
Couchbase is the namespace containing the client and configuration classes with
which you’ll work. Enyim.Caching.Memcached
contains supporting infrastructure. Because Couchbase supports the memcached protocol, it can
make use of the popular EnyimMemcached client for many of its core key-value
operations.
Next, create an instance of the client in the Main
method. Use the default
constructor, which depends on the configuration from the app.config file.
var client = new CouchbaseClient();
In practice, it’s expensive to create clients. The client incurs overhead as it creates connection pools and sets up the thread to get cluster configuration. Therefore, the best practice is to create a single client instance, per bucket, per AppDomain. Creating a static property on a class works well for this purpose. For example:
public static class CouchbaseManager
{
private readonly static CouchbaseClient _instance;
static CouchbaseManager()
{
_instance = new CouchbaseClient();
}
public static CouchbaseClient Instance { get { return _instance; } }
}
However, for the purpose of this getting started section, the locally scoped client variable created above is sufficient.
The primary CRUD API used by the .NET Client is a standard key-value
store. You create and update documents by supplying a key and value. You
retrieve or remove documents by supplying a value. For example, consider the following JSON document that you’ll find in the beer-sample
bucket that’s available when you install Couchbase Server and set up your cluster. The key for this document is new_holland_brewing_company-sundog
.
{
"name": "Sundog",
"abv": 5.25,
"ibu": 0,
"srm": 0,
"upc": 0,
"type": "beer",
"brewery_id": "new_holland_brewing_company",
"updated": "2010-07-22 20:00:20",
"description": "Sundog is an amber ale as deep as the copper glow of a Lake Michigan sunset. Its biscuit malt give Sundog a toasty character and a subtle malty sweetness. Sundog brings out the best in grilled foods, caramelized onions, nutty cheese, barbecue, or your favorite pizza.",
"style": "American-Style Amber/Red Ale",
"category": "North American Ale"
}
To retrieve this document, call the Get
method of the client:
var savedBeer = client.Get("new_holland_brewing_company-sundog");
If you add a line to print savedBeer
to the console, you should see a JSON
string that contains the data from the JSON document.
var savedBeer = client.Get("new_holland_brewing_company-sundog");
Console.WriteLine(savedBeer);
In this example, savedBeer
would be of type Object
. To get a string back instead and avoid having to cast, use the generic version of Get:
var savedBeer = client.Get<string>("new_holland_brewing_company-sundog");
To add a document, first create a JSON string:
var newBeer =
@"{
""name"": ""Old Yankee Ale"",
""abv"": 5.00,
""ibu"": 0,
""srm"": 0,
""upc"": 0,
""type"": ""beer"",
""brewery_id"": ""cottrell_brewing"",
""updated"": ""2012-08-30 20:00:20"",
""description"": ""A medium-bodied Amber Ale"",
""style"": ""American-Style Amber"",
""category"": ""North American Ale""
}";
In this example, the key is formed by taking the name of the beer and prefixing it with the name of the brewery, separated with a dash and replacing spaces replaced with underscores. The mechanism by which you create your keys needs to be consistent. If you are going to query documents by key (not just through views), you should choose predictable keys (for example, cottrell_brewing-old_yankee_ale
).
var key = "cottrell_brewing-old_yankee_ale";
You can store the document, as shown in the following example:
var result = client.Store(StoreMode.Add, "cottrell_brewing-old_yankee_ale", newBeer);
The Store
method takes several arguments. The first is the store mode. For the store mode value, use StoreMode.Add
for new keys, StoreMode.Replace
to update existing keys, and StoreMode.Set
to add when a key doesn’t exist or to replace it when it does. Store
fails if StoreMode.Add
is used with an existing key or StoreMode.Replace
is used with a nonexistent key. The second and third arguments are the key and value, respectively. The return value, assigned to result
in this example, is a Boolean that indicates whether the operation succeeded.
Removing a document entails calling the Remove
method with the key to be
removed. Like the other methods shown so far, Remove
returns a Boolean value
indicating operation success. For example:
var result = client.Remove("cottrell_brewing-old_yankee_ale");
While storing and retrieving JSON strings is a straightforward process, documents in a typical application are likely at some point to be represented by domain objects (that is, POCOs). More mileage comes from storing some representation of these data objects. For example, the beer documents could be represented by an instance of a Beer class in memory. The .NET Client Library allows for serializable objects to be persisted using .NET’s standard over-the-wire serialization. However, on the server, these objects are stored as binary attachments to a JSON document. The impact of being an attachment is that it is not indexed in a view. A better solution is to serialize data objects to JSON strings before storing them and deserializing JSON document strings to objects when retrieving them.
If you want an easy way to read and write JSON, the CouchbaseClientExtensions
class under the Couchbase.Extensions namespace provides two very basic methods, StoreJson
and GetJson
. Both methods depend on the open source Newtonsoft.Json library, which is already a dependency of the Couchbase .NET Library. Both methods wrap only the most basic Get and Store overloads and don’t currently support CAS or TTL operations. They are included with the library for convenience and might be augmented in the future by a Couchbase Labs extension library.
To improve the way beer data is managed in this getting started project, add a
new file, Beer.cs, to the project. It contains a plain-old-CLR-object (POCO)
with mappings from class properties to JSON properties. For brevity, some
document properties have been omitted. The Type
property is read-only and forces all beer instances to be marked with the type
beer
. This type information is useful when you want to create views and find all “beer” documents. The Beer.cs file looks like this:
public class Beer
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("abv")]
public float ABV { get; set; }
[JsonProperty("type")]
public string Type
{
get { return "beer"; }
}
[JsonProperty("brewery_id")]
public string BreweryId { get; set; }
[JsonProperty("style")]
public string Style { get; set; }
[JsonProperty("category")]
public string Category { get; set; }
}
By default, Json.NET serializes the properties of your class in the case you created them. Because we want our properties to match the casing of the documents in the beer-sample bucket, we’re going to set JSON property names in JsonProperty attributes (in the Newtonsoft.Json namespace). Again, we could store instances of this Beer class without converting them to JSON first (requires marking the class with a Serializable attribute), but that would prevent those documents from being indexed in views.
Persisting an instance as JSON is similar to how we persisted the JSON document string above. Replace the code where a JSON string was created with the code below.
var newBeer = new Beer
{
Name = "Old Yankee Ale",
ABV = 5.00f,
BreweryId = "cottrell_brewing",
Style = "American-Style Amber",
Category = "North American Ale"
};
To store the new instance, use the extension method. The returned result
object contains a Boolean that indicates whether the operation succeeded.
var result = client.StoreJson(StoreMode.Set, key, newBeer);
Retrieving the Beer instance is also similar to retrieving a document:
var savedBeer = client.GetJson<Beer>(key);
At this point, your Program.cs file should look something like the following example:
class Program
{
static void Main(string[] args)
{
var client = new CouchbaseClient();
var key = "cottrell_brewing-old_yankee_ale";
var newBeer = new Beer
{
Name = "Old Yankee Ale",
ABV = 5.00f,
BreweryId = "cottrell_brewing",
Style = "American-Style Amber",
Category = "North American Ale"
};
var result = client.StoreJson(StoreMode.Set, key, newBeer);
if (result)
{
var savedBeer = client.GetJson<Beer>(key);
Console.WriteLine("Found beer: " + savedBeer.Name);
}
}
}
MapReduce views are used to create secondary indexes in Couchbase Server that can be queried. The primary index for documents is the key you use when performing the standard CRUD methods described previously. See the view documentation for more information on writing views.
The following example queries the by_name
view in the beer design document.
This view just checks whether a document is a beer and has a name. If it does,
it emits the beer’s name into the index. This view allows for beers to be
queried by name. For example, it’s now possible to ask the question “What
beers start with A?”
function (doc, meta) {
if (doc.type && doc.type == "beer" && doc.name) {
emit(doc.name, null);
}
}
Querying a view through the .NET Client Library requires calling the GetView
method and providing the name of the design document and the name of the view.
var view = client.GetView("beer", "by_name");
The return type of GetView
is an enumerable IView
, where each enumerated value is an IViewRow
. The actual view query isn’t run until you enumerate over the view. For example, if you wanted to print out each of the keys that have been indexed, you could use the IViewRow
instance’s Info dictionary. This particular view emits null as the value, so that is empty when this snippet runs.
foreach (var row in view)
{
Console.WriteLine("Key: {0}, Value: {1}", row.Info["key"], row.Info["value"]);
}
The code above should give you a list of beer names for all beer documents that
exist in the beer-sample bucket. If you want to filter that list, there are
fluent methods that can be chained off of the IView
instance before iterating
over it. Modifying the GetView
call above as follows finds all beers whose
names start with “A” and limits the results to 50 rows. See the API reference
for other fluent methods. These methods return an IView
instance, which is an IEnumerable
, but is not an IQueryable
. Therefore, using
LINQ extension methods on the IView
does not reduce the results in the query.
Only the IView
fluent methods affect the query before it is run.
var view = client.GetView("beer", "by_name").StartKey("A").EndKey("B").Limit(50);
Also included in the IViewRow
instance, is the original ID (the key from the key-value pair) of the document. It is accessible by way of the ItemId
property of the IViewRow
. Taking that ID, it is possible to retrieve the original document. Using the JSON extension methods, it’s also possible to get a Beer instance for each row. If it seems expensive to perform these lookups, recall that Couchbase Server has a memcached layer built in, and these queries are unlikely to be pulling data from disk. The documents are likely to be found in memory.
foreach (var row in view)
{
var doc = client.GetJson<Beer>(row.ItemId);
Console.WriteLine(doc.Name);
}
Finally, there is a generic version of GetView
that encapsulates the details of
the view row data structures. To retrieve Beer instances automatically by ID as
you iterate over the view, you need to add the generic parameter to GetView
along with the third Boolean argument to tell the client to perform the by ID
lookup. If you omit the third parameter, the client attempts to deserialize
the value emitted by the index into an instance of the specified generic type.
Again, in this example the value was null. Therefore, deserialization must be
done by way of ID lookup.
var view = client.GetView<Beer>("beer", "by_name", true).StartKey("A").EndKey("B").Limit(50);
foreach (var beer in view)
{
Console.WriteLine(beer.Name);
}
For more information about using views for indexing and querying from Couchbase Server, see the following resources:
For more information on Views, how they operate, and how to write effective map/reduce queries, see Couchbase Server 2.0: Views and Couchbase Sever 2.0: Writing Views.
Sample Patterns: to see examples and patterns you can use for views, see Couchbase Views, Sample Patterns.
Timestamp Pattern: many developers frequently ask about extracting information based on date or time. To find out more, see Couchbase Views, Sample Patterns.
This tutorial walks you through the steps of creating an ASP.NET MVC app using Couchbase. It will focus on practical design patterns, best practices along with general SDK usage.
This tutorial assumes that you have Visual Studio 2010 installed, along with ASP.NET MVC 4. You may use any edition of Visual Studio or you may use Visual Web Developer. Visual Studio 2012 will also work for this tutorial, but the screenshots included will be from Visual Studio 2012 Professional.
You will also need to have an installation of Couchbase Server 2.0 and have obtained the latest Couchbase .NET Client Library, version 1.2 or higher. See “Getting Started” for more information on client installation.
You also may use an older version of ASP.NET MVC if you do not have MVC 4 installed, but as with using Visual Web Developer or Visual Studio 2012, the templates shown in the screenshots will vary from what you see.
You should also have installed the beer-sample database on your Couchbase Server. If you haven’t, simply open the web console and navigate to the “Settings” tab. There, you’ll find an option to add a sample bucket to your cluster.
This project will be based on an ASP.NET MVC 4 application template. After opening Visual Studio, select File -> New Project and then select Web -> ASP.NET MVC 4 Application under the Visual C# project templates. Name the project “CouchbaseBeersWeb” and click “OK” to create the solution.
Start with an “Empty” application using the Razor view engine for the MVC template.
Next you’ll need to add a reference to the Couchbase .NET Client Library. You could either download the assemblies from the getting started page or obtain them using the NuGet package manager. When you install via Nuget, your project will automatically get references to Couchbase.dll, Enyim.Caching.dll and the dependencies Newtonsoft.Json.dll and Hammock.dll. These assemblies are also found in the zip file and should be referenced in your project if not using Nuget.
The first task to solve is displaying a list of breweries in from our beer-sample bucket. To add this functionality, there is some plumbing to setup in our application. These tasks are enumerated below.
Create a Brewery
model class to represent beer documents
Create a BreweryRepository
to encapsulate data access for Brewery instances
Create a BreweriesController
with an Index action used to show a Brewery list
Create a Razor view to display our list of breweries
As a JSON document database, Couchbase supports a natural mapping of domain objects to data items. In other words, there’s very little difference between the representation of your data as a class in C# and the representation of your data as a document in Couchbase. Your object becomes the schema defined in the JSON document.
When working with domain objects that will map to documents in Couchbase, it’s useful, but not required, to define a base class from which your model classes will derive. This base class will be abstract and contain two properties, “Id” and “Type.”
Right click on the “Models” directory and add a new class named “ModelBase” and include the following code.
public abstract class ModelBase
{
public virtual string Id { get; set; }
public abstract string Type { get; }
}
Note that the Type method is abstract and readonly. It will be implemented by subclasses simply by returning a hard-coded string, typically matching the class name, lower-cased. The purpose of the Type property is to provide taxonomy to the JSON documents stored in your Couchbase bucket. The utility will be more obvious when creating views.
Next, create a new class namedin the “Models” directory of your project. This
class will be a plain old CLR object (POCO) that simply has properties mapping
to the properties of brewery documents in the beer-sample bucket. It will also
extend ModelBase
.
public class Brewery : ModelBase
{
public string Name { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Code { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public DateTime Updated { get; set; }
public string Description { get; set; }
public IList<string> Addresses { get; set; }
public IDictionary<string, object> Geo { get; set; }
public override string Type
{
get { return "brewery"; }
}
}
{
"name": "Thomas Hooker Brewing",
"city": "Bloomfield",
"state": "Connecticut",
"code": "6002",
"country": "United States",
"phone": "860-242-3111",
"website": "http://www.hookerbeer.com/",
"type": "brewery",
"updated": "2010-07-22 20:00:20",
"description": "Tastings every Saturday from 12-6pm, and 1st and 3rd Friday of every month from 5-8.",
"address": [
"16 Tobey Road"
],
"geo": {
"accuracy": "RANGE_INTERPOLATED",
"lat": 41.8087,
"lng": -72.7108
}
}
After creating the Brewery class, the next step is to create the data access
classes that will encapsulate our Couchbase CRUD and View operations. Create a
new file in “Models” named “RepositoryBase`1.cs” with a class name of
“RepositoryBase.” This will be an abstract class, generically constrained to
work with ModelBase
instances. The `1 suffix on the file name is a
convention used for generic classes in C# projects.
public abstract class RepositoryBase<T> where T : ModelBase
{
//CRUD methods
}
The process of creating an instance of a CouchbaseClient
is expensive. There
is a fair amount of overhead as the client establishes connections to the
cluster. It is therefore recommended to minimize the number of times that a
client instance is created in your application. The simplest approach is to
create a static property or singleton that may be accessed from data access
code. Using the RepositoryBase
, setting up a protected static property will
provide access for subclasses.
public abstract class RepositoryBase<T> where T : ModelBase
{
protected static CouchbaseClient _Client { get; set; }
static RepositoryBase()
{
_Client = new CouchbaseClient();
}
}
The code above requires setting configuration in web.config. It is of course equally valid to define the configuration in code if that is your preference. See getting started for more details.
<configSections>
<section name="couchbase" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase"/>
</configSections>
<couchbase>
<servers bucket="beer-sample">
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
</couchbase>
To display a list of all breweries, a view will be necessary. This map function for this view will simply emit null keys and values for each of the brewery documents in the database. This view will live in a “breweries” design document and be named “all.”
For more information on working with views in the admin console, see the documentation.
function(doc, meta) {
if (doc.type == "brewery") {
emit(null, null);
}
}
A null-key index still provides access to each of the document’s keys when the view is queried. Note however that range queries on keys would not be supported with this view.
You could create the “all” view above by creating a new design document in the
Couchbase web console or you could use the CouchbaseCluster
API ( see
docs
) found in Couchbase.dll to create and to save a design document. However, an
easier approach is to use the CouchbaseLabs
project Couchbase Model Views.
The Couchbase Model Views project is not part of the Client Library, but makes use of its design doc management API to create views from attributes placed on model classes. Using NuGet, add a reference to the CouchbaseModelViews package.
Once installed, modify the Brewery class definition to have two class level
attributes, CouchbaseDesignDoc
and CouchbaseAllView
.
[CouchbaseDesignDoc("breweries")]
[CouchbaseAllView]
public class Brewery : ModelBase
{
//props omitted for brevity
}
The CouchbaseDesignDoc
attribute instructs the Model Views framework to create
a design document with the given name. The CouchbaseAllView
will create the
“all” view as shown previously.
To get the Model Views framework to create the design doc and view, you’ll need
to register the assembly containing the models with the framework. In
Global.asax, create a static RegisterModelViews
method for this purpose.
public static void RegisterModelViews(IEnumerable<Assembly> assemblies)
{
var builder = new ViewBuilder();
builder.AddAssemblies(assemblies.ToList());
var designDocs = builder.Build();
var ddManager = new DesignDocManager();
ddManager.Create(designDocs);
}
Then in the existing Application_Start
method, add a line to register the
current assembly.
RegisterModelViews(new Assembly[] { Assembly.GetExecutingAssembly() });
Note that the Model Views framework will create the design doc only if it has changed, so you don’t have to worry about your indexes being recreated each time your app starts.
To test that the Model Views framework is working, simply run the application (Ctrl + F5). If all went well, you should be able to navigate to the “Views” tab in the Couchbase web console and see the new design doc and view in the “Production Views” tab (as shown below).
If you click the link next to the “Filter Results” button, you will see the JSON
that is returned to the CouchbaseClient
when querying a view. Notice the “id”
property found in each row. That is the key that was used to store the document.
{"total_rows":1412,"rows":[
{"id":"21st_amendment_brewery_cafe","key":null,"value":null},
{"id":"357","key":null,"value":null},
{"id":"3_fonteinen_brouwerij_ambachtelijke_geuzestekerij","key":null,"value":null},
{"id":"512_brewing_company","key":null,"value":null},
{"id":"aass_brewery","key":null,"value":null},
{"id":"abbaye_de_leffe","key":null,"value":null},
{"id":"abbaye_de_maredsous","key":null,"value":null},
{"id":"abbaye_notre_dame_du_st_remy","key":null,"value":null},
{"id":"abbey_wright_brewing_valley_inn","key":null,"value":null},
{"id":"aberdeen_brewing","key":null,"value":null}
]
}
With the view created, the next step is to modify the RepositoryBase
to have a
GetAll
method. This method will use some conventions to allow for reuse across
subclasses. One of those conventions is that queries will be made to design docs
with camel-cased and pluralized names (e.g., Brewery to breweries). To aid in
the pluralization process, create a reference to inflector_extension using
NuGet. Note that in .NET 4.5, there is a PluralizationService
class that will
provide some of the same support.
To the RepositoryBase
class, add a readonly private field and initialize it to
the inflected and pluralized name of the type of T. The inflector extension
methods will require an additional using statement.
using inflector_extension;
private readonly string _designDoc;
public RepositoryBase()
{
_designDoc = typeof(T).Name.ToLower().InflectTo().Pluralized;
}
The initial implementation of GetAll
will simply return all breweries using
the generic GetView<T>
method of CouchbaseClient
. The third parameter
instructs CouchbaseClient
to retrieve the original document rather than
deserialize the value of the view row.
public virtual IEnumerable<T> GetAll()
{
return _Client.GetView<T>(_designDoc, "all", true);
}
RepositoryBase
is a generic and abstract class, so obviously it cannot be used
directly. Create a new class in “Models” named “BreweryRepository.” The code for
this class is very minimal, as it will rely on its base class for most
functionality.
public class BreweryRepository : RepositoryBase<Brewery>
{
}
With the models and repository coded, the next step is to create the controller. Right click on the “Controllers” directory in the project and select Add -> Controller. Name the controller “BreweriesController” and select the template “Controller with empty read/write actions,” which will create actions for creating, updating, deleting, showing and listing breweries.
The Index method of the BreweriesController
will be used to display the list
of breweries. To allow the new controller to access brewery data, it will need
an instance of a BreweryRepository
. Create a public property of type
BreweryRepository
and instantiate it in the default constructor.
public BreweryRepository BreweryRepository { get; set; }
public BreweriesController()
{
BreweryRepository = new BreweryRepository();
}
Then inside of the Index
method, add a call to BreweryRepository
’s GetAll
method and pass its results to the view as its model.
public ActionResult Index()
{
var breweries = BreweryRepository.GetAll();
return View(breweries);
}
The last step to displaying the list of breweries is to create the Razor view
(as in MVC views, not Couchbase views). In the “Views” directory, create a new
directory named “Breweries.” Right click on that new directory and select “Add”
-> “View.” Name the view “Index” and create it as a strongly typed (to the
Brewery
class) view with List scaffolding. This template will create a Razor
view that loops over the brewery results, displaying each as a row in an HTML
table.
At this point, you should build your application and navigate to the Breweries path (e.g., http://localhost:52962/breweries ). If all went well, you should see a list of breweries.
There are quite a few breweries being displayed in this list. Paging will be an eventual improvement, but for now limiting the results by modifying the defaults of the GetAll method will be sufficient.
//in RepositoryBase
public IEnumerable<T> GetAll(int limit = 0)
{
var view = _Client.GetView<T>(_designDoc, "all", true);
if (limit > 0) view.Limit(limit);
return view;
}
//in BreweriesController
public ActionResult Index()
{
var breweries = BreweryRepository.GetAll(50);
return View(breweries);
}
For more information about using views for indexing and querying from Couchbase Server, here are some useful resources:
For technical details on views, how they operate, and how to write effective map/reduce queries, see Couchbase Server 2.0: Views and Couchbase Sever 2.0: Writing Views.
Sample Patterns: to see examples and patterns you can use for views, see Couchbase Views, Sample Patterns.
Timestamp Pattern: many developers frequently ask about extracting information based on date or time. To find out more, see Couchbase Views, Sample Patterns.
The MVC scaffolding that created the Razor template to list breweries also included links to create, show, edit and delete breweries. Using more scaffolding, these CRUD features are easily implemented.
Create and Update methods require a bit of effort to encapsulate. One decision
to make is whether to use the detailed result ExecuteStore
method or the
Boolean>
Store method of the Client. ExecuteStore
returns an instance of an
IStoreOperationResult
, which contains a success status and error message
properties, among others.
Since it is likely important to know whether operations succeeded,
ExecuteStore
will be used in our RepositoryBase
. However, that interface
will be hidden from the application and instead an int will be returned by each
method. The int will be the status code returned by Couchbase Server for each
operation.
public virtual int Create(T value){}
public virtual int Update(T value) {}
public virtual int Save(T value) {}
There are other implementation details that need to be considered when implementing these methods, namely key creation and JSON serialization.
CRUD operations in Couchbase are performed using a key/value API. The key that is used for these operations may be either meaningful (i.e., human readable) or arbitrary (e.g., a GUID). When made human readable, your application may be able to make use of predictable keys to perform key/value get operations (as opposed to secondary indexes by way of view operations).
A common pattern for creating readable keys is to take a unique property, such
as Brewery.Name
, and replace its spaces, possibly normalizing to lowercase. So
“Thomas Hooker Brewery” becomes “thomas_hooker_brewery.”
Add the following BuildKey
method to the RepositoryBase
to allow for default
key creation based on the Id
property.
protected virtual string BuildKey(T model)
{
if (string.IsNullOrEmpty(model.Id))
{
return Guid.NewGuid().ToString();
}
return model.Id.InflectTo().Underscored;
}
BuildKey
will default to a GUID
string when no Id is provided. It’s also
virtual so that subclasses are able to override the default behavior. The
BreweryRepository
needs to override the default behavior to provide a key
based on brewery name.
protected override string BuildKey(Brewery model)
{
return model.Name.InflectTo().Underscored;
}
When storing a Brewery
instance in Couchbase Server, it first has to be
serialized into a JSON string. An important consideration is how to map the
properties of the Brewery
to properties of the JSON document.
JSON.NET (from Newtonsoft.Json) will by default serialize all properties.
However, ModelBase
objects all have an Id property that shouldn’t be
serialized into the stored JSON. That Id is already being used as the document’s
key (in the key/value operations), so it would be redundant to store it in the
JSON.
JSON.NET supports various serialization settings, including which properties
should be included in serialization. In RepositoryBase
, create a
serializAndIgnoreId
method and a private DocumentIdContractResolver
class as
shown below.
private string serializeAndIgnoreId(T obj)
{
var json = JsonConvert.SerializeObject(obj,
new JsonSerializerSettings()
{
ContractResolver = new DocumentIdContractResolver(),
});
return json;
}
private class DocumentIdContractResolver : CamelCasePropertyNamesContractResolver
{
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
return base.GetSerializableMembers(objectType).Where(o => o.Name != "Id").ToList();
}
}
The DocumentIdContractResolver
will prevent the Id property from being saved
into the JSON. It also extends CamelCasePropertyNamesContractResolver
to
provide camel-cased properties in the JSON output.
Note that there is a JsonIgnore
attribute that could be added to properties
that should be omitted from the serialized JSON, however it is less global in
its application. For example, if a class overrides the Id
property of
ModelBase
, it would have to add the attribute.
With this new plumbing in place, it’s now possible to complete the Create
,
Update
and Save
methods. Exceptions are caught and wrapped in the
IStoreOperationResult’s Exception property. If an exception is detected, it will
be thrown up to the caller. These new methods also have an optional durability
argument, which will block until a document has been written to disk, or the
operation times out. By default, there is no durability requirement imposed.
public virtual int Create(T value, PersistTo persistTo = PersistTo.Zero)
{
var result = _Client.ExecuteStore(StoreMode.Add, BuildKey(value), serializeAndIgnoreId(value), persistTo);
if (result.Exception != null) throw result.Exception;
return result.StatusCode.Value;
}
public virtual int Update(T value, PersistTo persistTo = PersistTo.Zero)
{
var result = _Client.ExecuteStore(StoreMode.Replace, value.Id, serializeAndIgnoreId(value), persistTo);
if (result.Exception != null) throw result.Exception;
return result.StatusCode.Value;
}
public virtual int Save(T value, PersistTo persistTo = PersistTo.Zero)
{
var key = string.IsNullOrEmpty(value.Id) ? BuildKey(value) : value.Id;
var result = _Client.ExecuteStore(StoreMode.Set, key, serializeAndIgnoreId(value), persistTo);
if (result.Exception != null) throw result.Exception;
return result.StatusCode.Value;
}
The Get
method of RepositoryBase
requires similar considerations.
CouchbaseClient.ExecuteGet
returns an IGetOperationResult
. To be consistent
with the goal of not exposing Couchbase SDK plumbing to the app, Get
will
return the object or null if not found, while throwing a swallowed exception.
Notice also that the Id
property of the model is set to the value of the key,
since it’s not being stored in the JSON.
public virtual T Get(string key)
{
var result = _Client.ExecuteGet<string>(key);
if (result.Exception != null) throw result.Exception;
if (result.Value == null)
{
return null;
}
var model = JsonConvert.DeserializeObject<T>(result.Value);
model.Id = key; //Id is not serialized into the JSON document on store, so need to set it before returning
return model;
}
Completing the CRUD operations is the Delete
method. Delete
will also hide
its SDK result data structure ( IRemoveOperationResult
) and return a status
code, while throwing swallowed exceptions. Delete also supports the durability
requirement overload.
public virtual int Delete(string key, PersistTo persistTo = PersistTo.Zero)
{
var result = _Client.ExecuteRemove(key, persistTo);
if (result.Exception != null) throw result.Exception;
return result.StatusCode.HasValue ? result.StatusCode.Value : 0;
}
With the new methods implemented, it’s time to create the scaffolding for the
CRUD forms. The first task will be to create an edit form. Open the
BreweriesController
and locate the Edit
methods that were generated by the
Add Controller wizard.
In the HTTP GET override of Edit
, modify it as shown below. This action will
retrieve the Brewery
and pass it to the view as the model. Note the change
from an int id parameter to a string id parameter.
public ActionResult Edit(string id)
{
var brewery = BreweryRepository.Get(id).Item1;
return View(brewery);
}
Update the Edit
method that handles POSTs as shown below. Validation and error
handling are intentionally being omitted for brevity.
[HttpPost]
public ActionResult Edit(string id, Brewery brewery)
{
BreweryRepository.Update(brewery);
return RedirectToAction("Index");
}
The edit form will be created using scaffolding, as was the case with the listing page. Right click on the “Breweries” folder in the “Views” directory and click Add -> View. Name the view “Edit” and strongly type it to a Brewery with Edit scaffolding.
Rebuild the application and return to the brewery listing page. Click on an “Edit” link and you should see the edit form loaded with the details for that brewery. Edit some values on the form and click save. You should see your changes persisted on the listing page.
The Details
action looks very much like Edit
. Get the Brewery
and provide
it as the model for the view.
public ActionResult Details(string id)
{
var brewery = BreweryRepository.Get(id).Item1;
return View(brewery);
}
Create a scaffolding form for Details
using the same process as was used with
Edit
.
Rebuild and return to the list page. Click on a “Details” link. You should see a page listing the data for that brewery.
The Create
and Edit
actions of the BreweriesController
are quite similar,
save for the fact that Create’s GET method doesn’t provide a model to the view.
Again, error handling and validation are being omitted for brevity’s sake.
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Brewery brewery)
{
BreweryRepository.Create(brewery);
return RedirectToAction("Index");
}
Go through the scaffolding process again to add a create view for the Create
action. Rebuild and click the “Create New” link on the list page to test the
new form. Breweries (for now) are sorted by key and limited to 50, so you might
not see yours in the list. If you want to verify your create action worked, use
brewery name that starts with a numeric value (e.g., 123 Brewery).
Another reason you wouldn’t see your new brewery appear in the list of breweries is that the view is set to allow stale (eventually consistent) results. In other words, the incremental update to the “all” index would be performed after the query. If you refresh, you should see your brewery in the list.
Allowing the breweries to be sorted by key is convenient, since the key is based on the breweries name. However, if case-sensitivity is important in sorting or the key creation strategy changes, then explicitly sorting on the brewery’s name is a better idea. To that end, creating a new view indexed on the Brewery name is the right approach.
The new map function will look similar to the “all” map function, but will add tests on “doc.name” and will emit the doc.name as the key.
function(doc, meta) {
if (doc.type == "brewery" && doc.name) {
emit(doc.name, null);
}
}
If you are using the web console to manage your design documents, save the map
function above as “by_name” in the “breweries” design document. If you are
using the Model Views framework, add an attribute to the Name
property of
Brewery>
. Then compile and run your application.
Adding the CouchbaseViewKey
attribute will create the view above. The first
argument is the name of the view. The second is the name of the JSON document
property to emit as key.
[CouchbaseViewKey("by_name", “name”)]
public string Name { get; set; }
The next step is to replace the GetAll call with a call to the new view. First, add a protected method in RepositoryBase that returns a typed view instance, set with the design doc for that model type. The isProjection flag is set when the type of T does not properties of the JSON to properties of the class. It must be used with explicit JSON.NET mappings.
protected IView<T> GetView(string name, bool isProjection = false)
{
return _Client.GetView<T>(_designDoc, name, ! isProjection);
}
Then in BreweryRepository
, implement GetAllByName
as shown below. This new
method simply returns the view, optionally allowing a limit and stale results.
public IEnumerable<Brewery> GetAllByName(int limit = 0, bool allowStale = false)
{
var view = GetView("by_name");
if (limit > 0) view.Limit(limit);
if (! allowStale) view.Stale(StaleMode.False);
return view;
}
Next, modify the BreweriesController
so that the Index
action calls the new
GetAllByName
method.
public ActionResult Index()
{
var breweries = BreweryRepository.GetAllByName(50);
return View(breweries);
}
Compile and run your application. The list page might be ordered a little differently as the sample database did scrub some keys of punctuation and other non-word or non-digit characters. Also now (because of the stale setting), if you create a new Brewery, it should appear after a redirect and should not require a refresh.
Note that it is still possible that the view didn’t consider the new Brewery when it was executed with state set to false. If the document hadn’t persisted to disk before the index was updated, it wouldn’t have been included.
If that level of consistency is important to your application, you should use an
overload of ExecuteStore
that includes durability requirements. See the
documentation on ExecuteStore for more information.
The last piece required to complete the CRUD functionality for breweries is to
implement the delete form. Update
the Delete
actions in
BreweriesController
as shown below.
public ActionResult Delete(string id)
{
var brewery = BreweryRepository.Get(id).Item1;
return View(brewery);
}
[HttpPost]
public ActionResult Delete(string id, Brewery brewery)
{
BreweryRepository.Delete(id);
return RedirectToAction("Index");
}
To create the delete form, simply go through the Add View process again and
choose scaffolding for delete (don’t forget to choose the Brewery
model).
At this point, you’ve written a full CRUD app for breweries in the beer-sample database. Another optimization we might want to include is to show the names of beers that belong to a particular brewery. In the relational world, this is typically accomplished using a join between two tables. In Couchbase, the solution is to use a collated view.
Before looking at the map function for this view, it’s useful to inspect a beer document.
{
"name": "Old Stock Ale 2004",
"abv": 11.4,
"ibu": 0,
"srm": 0,
"upc": 0,
"type": "beer",
"brewery_id": "north_coast_brewing_company",
"updated": "2010-07-22 20:00:20",
"description": "",
"style": "Old Ale",
"category": "British Ale"
}
Note the “brewery_id” property. This is the key of a brewery document and can be thought of as a “foreign key.” Note that this type of document foreign key relationship is not enforced by Couchbase.
The basic idea behind a collated view is to produce an index in which the keys are ordered so that a parent id appears first, followed by its children. In the beer-sample case that means a brewery appears in a row followed by rows of beers.
The basic algorithm for the map function is to check the doc.type. If a brewery is found, emit its key (meta.id). If a child is found, emit its parent id (brewery_id). The map function for the view “all_with_beers” is shown below.
function(doc, meta) {
switch(doc.type) {
case "brewery":
emit([meta.id, 0]);
break;
case "beer":
if (doc.name && doc.brewery_id) {
emit([doc.brewery_id, doc.name, 1], null);
}
}
}
The trick to ordering properly the parent/child keys is to use a composite key in the index. Parent ids are paired with a 0 and children with a 1. The collated order of the view results is shown conceptually below.
A Brewery, 0
A Brewery, 1
A Brewery, 1
B Brewery, 0
B Brewery, 1
To use Model Views to create this view, simply add an attribute to an overridden Id property on the Brewery class.
[CouchbaseCollatedViewKey("all_with_beers", "beer", "brewery_id", "name")]
public override string Id { get; set; }
This is a good time to introduce a simple Beer
class, of which Brewery will
have a collection. Create a new model class named “Beer.” For now, include only
the Name
property.
public class Beer : ModelBase
{
public string Name { get; set; }
public override string Type
{
get { return "beer"; }
}
}
Then add a Beer
list property to Brewery
. This property shouldn’t be
serialized into the doc, so add the JsonIgnore
attribute.
private IList<Beer> _beers = new List<Beer>();
[JsonIgnore]
public IList<Beer> Beers
{
get { return _beers; }
set { _beers = value; }
}
Since the collated view has a mix of beers and breweries, the generic
GetView<T>
method won’t work well for deserializing rows. Instead, we’ll use
the GetView method that returns IViewRow
instances. First add a new
GetViewRaw
method to RepositoryBase
.
protected IView<IViewRow> GetViewRaw(string name)
{
return _Client.GetView(_designDoc, name);
}
Then in BreweryRepository
, add a GetWithBeers
method to build the object
graph. This new method performs a range query on the view, starting with the
brewery id and including all possible beer names for that brewery.
public Tuple<Brewery, bool, string> GetWithBeers(string id)
{
var rows = GetViewRaw("all_with_beers")
.StartKey(new object[] { id, 0 })
.EndKey(new object[] { id, "\uefff", 1 })
.ToArray();
var result = Get(rows[0].ItemId);
result.Item1.Beers = rows.Skip(1)
.Select(r => new Beer { Id = r.ItemId, Name = r.ViewKey[1].ToString() })
.ToList();
return result;
}
Update the Details
method of BreweriesController
to use this method.
public ActionResult Details(string id)
{
var brewery = BreweryRepository.GetWithBeers(id).Item1;
return View(brewery);
}
Before the closing fieldset
tag in details template, add a block of Razor code
to display the beers.
<div class="display-field">Beers</div>
<div>
@foreach (var item in Model.Beers)
{
<div style="margin-left:10px;">- @item.Name</div>
}
</div>
The final feature to implement on the brewery CRUD forms is paging. It’s important to state up front that paging in Couchbase does not work like paging in a typical RDBMS. Though views have skip and limit filters that could be used create the standard paging experience, it’s not advisable to take this approach.
The skip filter still results in a read of index data starting with the first row of the index. For example, if an index has 5000 rows and skip is set to 500 and limit is set to 50, 500 records are read and 50 returned. Instead, linked-list style pagination is the recommended approach. Paging should also consider the document ids because keys may collide. However, in the breweries example, paging on name is safe because name is the source of the unique key.
First add an HTML footer to the list table in the Index view, right before the final closing table tag. There is a link back to the first page and links to the previous and next pages. A default page size of 10 is also used. Each time the page is rendered, it sets the previous key to the start key of the previous page. The next key will be explained shortly.
<tr>
<td colspan="4">
@Html.ActionLink("List", "Index", new { pagesize = 10 })
@Html.ActionLink("< Previous", "Index", new { startKey = Request["previousKey"], pagesize = Request["pagesize"] ?? "10" })
@Html.ActionLink("Next >", "Index", new { startKey = ViewBag.NextStartKey, previousKey = ViewBag.StartKey, pagesize = Request["pagesize"] ?? "10"})
</td>
</tr>
Modify the GetAllByName
method in BreweryRepository
to be able to handle
range queries (startkey, endkey).
public IEnumerable<Brewery> GetAllByName(string startKey = null, string endKey = null, int limit = 0, bool allowStale = false)
{
var view = GetView("by_name");
if (limit > 0) view.Limit(limit);
if (! allowStale) view.Stale(StaleMode.False);
if (! string.IsNullOrEmpty(startKey)) view.StartKey(startKey);
if (! string.IsNullOrEmpty(endKey)) view.StartKey(endKey);
return view;
}
For the actual paging, modify the BreweryController
’s Index method to keep
track of pages. The trick is to select page size + 1 from the view. The last
element is not rendered, but its key is used as the start key of the next page.
In simpler terms, the start key of the current page is the next page’s previous
key. The last element’s key is not displayed, but is used as the next page’s
start key.
public ActionResult Index(string startKey, string nextKey, int pageSize = 25)
{
var breweries = BreweryRepository.GetAllByName(startKey: startKey, limit: pageSize+1);
ViewBag.StartKey = breweries.ElementAt(0).Name;
ViewBag.NextStartKey = breweries.ElementAt(breweries.Count()-1).Name;
return View(breweries.Take(pageSize));
}
At this point, breweries may be created, detailed (with Children), listed, updated and deleted. The next step is to look at the brewery data from a different perspective, namely location.
Brewery documents have multiple properties related to their location. There are state and city properties, as well as detailed geospatial data. The first question to ask of the data is how many breweries exist for a given country. Then within each country, the counts can be refined to see how many breweries are in a given state, then city and finally zip code. All of these questions will be answered by the same view.
Create a view named “by_country” with the code below. This view will not consider documents that don’t have all location properties. The reason for this restriction is so that counts are accurate as you drill into the data.
function (doc, meta) {
if (doc.country && doc.state && doc.city && doc.code) {
emit([doc.country, doc.state, doc.city, doc.code], null);
}
}
For this view, you’ll also want a reduce function, which will count the number of rows for a particular grouping by counting how many rows appear for that grouping. So for example, when the group_level parameter is set to 2 brewery counts will be returned by city and state. For an analogy, think of a SQL statement selecting a COUNT(*) and having a GROUP BY clause with city and state columns.
Couchbase has three built in reduce functions - _count, _sum and _stats. For this view, _count and _sum will perform the same duties. Emitting a 1 as a value means that _sum would sum the 1s for a grouping. _count would simply count 1 for each row, even with a null value.
If you are using Model Views, then simply add CouchbaseViewKeyCount
attributes
to each of the properties that should be produced in the view.
[CouchbaseViewKeyCount("by_country", "country", 0, null)]
public string Country { get; set; }
[CouchbaseViewKeyCount("by_country", "state", 1)]
public string State { get; set; }
[CouchbaseViewKeyCount("by_country", "city", 2)]
public string City { get; set; }
[CouchbaseViewKeyCount("by_country", "code", 3)]
public string Code { get; set; }
This view demonstrates how to create ordered, composite keys from domain object properties using the Model Views framework.
The next step is to modify the BreweryRepository
to include methods that will
return aggregated results grouped at the appropriate levels. This new method
will return key value pairs where the key is the lowest grouped part of the key
and the value is the count. Also add an enum for group levels.
public IEnumerable<KeyValuePair<string, int>> GetGroupedByLocation(BreweryGroupLevels groupLevel, string[] keys = null)
{
var view = GetViewRaw("by_country")
.Group(true)
.GroupAt((int)groupLevel);
if (keys != null)
{
view.StartKey(keys);
view.EndKey(keys.Concat(new string[] { "\uefff" }));
}
foreach (var item in view)
{
var key = item.ViewKey[(int)groupLevel-1].ToString();
var value = Convert.ToInt32(item.Info["value"]);
yield return new KeyValuePair<string, int>(key, value);
}
}
Create a new controller named “CountriesController” to contain the actions for the new grouped queries. Use the empty controller template.
Modify the new controller to include the code below, which sets up the
BreweryRepositoryReference
and loads sends the view results to the MVC View.
public class CountriesController : Controller
{
public BreweryRepository BreweryRepository { get; set; }
public CountriesController()
{
BreweryRepository = new BreweryRepository();
}
public ActionResult Index()
{
var grouped = BreweryRepository.GetGroupedByLocation(BreweryGroupLevels.Country);
return View(grouped);
}
}
Next create a new directory under “Views” named “Countries.” Add a view named “Index” that is not strongly typed.
To the new view, add the Razor code below, which will simply display the keys and values as a list. It also links to the Provinces action, which you’ll create next.
@model dynamic
<h2>Brewery counts by country</h2>
<ul>
@foreach (KeyValuePair<string, int> item in Model)
{
<li>
@Html.ActionLink(item.Key, "Provinces", new { country = item.Key})
(@item.Value)
</li>
}
</ul>
Build and run your application and you should see a page like below.
Next, add the Provinces
action to the CountriesController
. This action will
reuse the repository method, but will change the group level to Province (2) and
pass the selected country to be used as a key to limit the query results.
public ActionResult Provinces(string country)
{
var grouped = BreweryRepository.GetGroupedByLocation(
BreweryGroupLevels.Province, new string[] { country } );
return View(grouped);
}
Create another empty view named “Provinces” in the “Countries” directory under the “Views” directory. Include the content below, which is similar to the index content.
@model dynamic
<h2>Brewery counts by province in @Request["country"]</h2>
<ul>
@foreach (KeyValuePair<string, int> item in Model)
{
<li>
@Html.ActionLink(item.Key, "Cities",
new { country = Request["country"], province = item.Key})
(@item.Value)
</li>
}
</ul>
Compile and run the app. You should see the Provinces page below.
Creating the actions and views for cities and codes is a similar process. Modify
CountriesController
to include new action methods as shown below.
public ActionResult Cities(string country, string province)
{
var grouped = BreweryRepository.GetGroupedByLocation(
BreweryGroupLevels.City, new string[] { country, province });
return View(grouped);
}
public ActionResult Codes(string country, string province, string city)
{
var grouped = BreweryRepository.GetGroupedByLocation(
BreweryGroupLevels.PostalCode, new string[] { country, province, city });
return View(grouped);
}
Then add a view named “Cities” with the Razor code below.
@model dynamic
<h2>Breweries counts by city in @Request["province"], @Request["country"]</h2>
<ul>
@foreach (KeyValuePair<string, int> item in Model)
{
<li>
@Html.ActionLink(item.Key, "Codes",
new { country = Request["country"],
province = Request["province"],
city = item.Key})
(@item.Value)
</li>
}
</ul>
Then add a view named “Codes” with the Razor code below.
@model dynamic
<h2>Brewery counts by postal code in @Request["city"], @Request["province"], @Request["country"]</h2>
<ul>
@foreach (KeyValuePair<string, int> item in Model)
{
<li>
@Html.ActionLink(item.Key, "Details",
new { country = Request["country"],
province = Request["province"],
city = Request["city"],
code = item.Key})
(@item.Value)
</li>
}
</ul>
@Html.ActionLink("Back to Country List", "Index")
Compile and run the app. Navigate through the country and province listings to the cities listing. You should see the page below.
Click through to the codes page and you should see the page below.
The last step for this feature is to display the list of breweries for a given
zip code. To implement this page, you need to add a new method to
BreweryRepository
named GetByLocation
. This method will use the same view
that we’ve been using, except it won’t execute the reduce step. Not executing
the reduce step means that the results come back ungrouped and individual items
are returned.
public IEnumerable<Brewery> GetByLocation(string country, string province, string city, string code)
{
return GetView("by_country").Key(new string[] { country, province, city, code }).Reduce(false);
}
Then add a Details
action method to the BreweriesController
that calls this
method and returns its results to the view.
public ActionResult Details(string country, string province, string city, string code)
{
var breweries = BreweryRepository.GetByLocation(country, province, city, code);
return View(breweries);
}
Create a Details view in the “Countries” folder with the Razor code below.
@model IEnumerable<CouchbaseBeersWeb.Models.Brewery>
<h2>Breweries in @Request["code"], @Request["city"], @Request["province"], @Request["country"]</h2>
<ul>
@foreach (var item in Model)
{
<li>
@Html.ActionLink(item.Name, "Details", "Breweries", new { id = item.Id }, new { })
</li>
}
</ul>
@Html.ActionLink("Back to Country List", "Index")
Compile and run the app. Click through country, province and state on to the Codes view. The code above already has a link to this new Details page. When you click on a postal code, you should see a list of breweries as below.
The last feature to add to the brewery app is the ability to search for breweries using its longitude and latitude. The experimental spatial indexes in Couchbase allow for bounding box searches. Spatial indexes are created by emitting a GeoJSON document as the key. This document must contain a coordinates property to be indexed.
Using the web console, create a new spatial view (click “Add Spatial View”) in the breweries design document named “points” using the spatial function below. Note that spatial views do not use the same map/reduce process as standard views.
function (doc, meta) {
if (doc.type == "brewery" && doc.geo.lng && doc.geo.lat) {
emit({ "type": "Point", "coordinates": [doc.geo.lng, doc.geo.lat]}, null);
}
}
If you are using Model Views, then you’ll need to modify the Brewery
class.
Currently, the Model Views framework doesn’t support object graph navigation, so
you’ll need to flatten the “geo” property of the JSON document into the
Brewery
as shown below. These flattened properties provide Model Views with a
way to build the spatial index.
[JsonIgnore]
public string GeoAccuracy
{
get
{
return Geo != null && Geo.ContainsKey("accuracy") ? Geo["accuracy"] as string : "";
}
}
[CouchbaseSpatialViewKey("points", "geo.lng", 0)]
[JsonIgnore]
public float Longitude
{
get
{
return Geo != null && Geo.ContainsKey("lng") ? Convert.ToSingle(Geo["lng"]) : 0f;
}
}
[CouchbaseSpatialViewKey("points", "geo.lat", 1)]
[JsonIgnore]
public float Latitude
{
get
{
return Geo != null && Geo.ContainsKey("lat") ? Convert.ToSingle(Geo["lat"]) : 0f;
}
}
Next, RepositoryBase
should be modified to provide support for generic views.
As with GetView
and GetViewRaw
, these new methods are to provide some code
reuse to subclasses.
protected virtual ISpatialView<T> GetSpatialView(string name, bool isProjection = false)
{
return _Client.GetSpatialView<T>(_designDoc, name, !isProjection);
}
protected virtual ISpatialView<ISpatialViewRow> GetSpatialViewRaw(string name)
{
return _Client.GetSpatialView(_designDoc, name);
}
Then update BreweryRepository
with a method to call the new “points” view.
Spatial views expect a bounding box with lower left and upper right coordinates,
ordered longitude then latitude. The UI will work with a delimited string, so
those points must be parsed and parsed as floats.
public IEnumerable<Brewery> GetByPoints(string boundingBox)
{
var points = boundingBox.Split(',').Select(s => float.Parse(s)).ToArray();
return GetSpatialView("points").BoundingBox(points[0], points[1], points[2], points[3]);
}
Then add a new class named “LocationsController” to the “Controllers” folder using the code below.
public class LocationsController : Controller
{
public BreweryRepository BreweryRepository { get; set; }
public LocationsController()
{
BreweryRepository = new BreweryRepository();
}
[HttpGet]
public ActionResult Details()
{
return View();
}
[HttpPost]
public ActionResult Details(string bbox)
{
var breweriesByPoints = BreweryRepository.GetByPoints(bbox)
.Select(b => new
{
id = b.Id,
name = b.Name,
geo = new float[] { b.Longitude, b.Latitude }
});
return Json(breweriesByPoints);
}
}
Most of the code above is boilerplate. A BreweryRepository
is declared and
initialized in the default constructor. The Details action that handles GET
requests simply returns the view. The Details request that handles POST requests
calls the new BreweryRepository method and renders a JSON array of brewery
projections that will be used in the view.
Next create a new Views folder called “Locations” and add a new view named “Details” to it. This new view will make use of Nokia’s HERE location services API. You can register for free at http://here.com. Add the Razor and JavaScript code below to your view.
@model CouchbaseBeersWeb.Models.Brewery
<style type="text/css">
#mapContainer {
width: 80%;
height: 768px;
margin-left:10%;
margin-right:10%;
}
</style>
<script type="text/javascript" charset="UTF-8" src="http://api.maps.nokia.com/2.2.3/jsl.js?with=all"></script>
<h2>View Breweries</h2>
<div id="mapContainer"></div>
<script type="text/ecmascript">
nokia.Settings.set("appId", "<YOUR APP ID>");
nokia.Settings.set("authenticationToken", "<YOUR TOKEN>");
var mapContainer = document.getElementById("mapContainer");
var map = new nokia.maps.map.Display(mapContainer, {
center: [41.763309, -72.67408],
zoomLevel: 8,
components: [
new nokia.maps.map.component.Behavior()
]
});
$().ready(function () {
var loadBreweries = function () {
var mapBoundingBox = map.getViewBounds();
var queryBoundingBox = mapBoundingBox.topLeft.longitude + "," + mapBoundingBox.bottomRight.latitude + "," +
mapBoundingBox.bottomRight.longitude + ","
+ mapBoundingBox.topLeft.latitude;
$.post("@Url.Action("Details")", { bbox: queryBoundingBox }, function (data) {
var coordinates = new Array();
$.each(data, function (idx, item) {
var coordinate = new nokia.maps.geo.Coordinate(item.geo[1], item.geo[0]);
var standardMarker = new nokia.maps.map.StandardMarker(coordinate);
map.objects.add(standardMarker);
});
});
};
map.addListener("dragend", function (evt) {
loadBreweries();
});
loadBreweries();
});
</script>
The details of the HERE API are beyond the scope of this tutorial. The basic
idea though is that when the map is rendered, the bounding box coordinates are
obtained and passed (via AJAX) to the Details POST method on the
LocationsController
. The coordinates that come back are used to render points
on the map via a standard marker.
Compile and run these last changes. Navigate to /locations/details and you should see a map such as the one shown below.
At this point, the brewery features are complete. Creating a set of pages for the beer documents is a similar exercise that is left to the reader. Using scaffolding and reusing the patterns from working with breweries, it shouldn’t take much effort to build those features.
The code for this sample app is on GitHub at https://github.com/couchbaselabs/beer-sample-net. It contains all the code from this tutorial, plus the beer pages. It also contains some very minor style and navigation improvements (such as a home page).
Finally, a single tutorial can address only so many concerns. Clearly some shortcuts were taken with validation, exception handling and the like. Certain architectural patterns, such as dependency injection and MVVM were also omitted for the sake of brevity. The intent of this tutorial was to provide an intermediate introduction to Couchbase development with .NET. Your app should be able to make use of some or all of the patterns described.
This section describes the .NET client APIs.
API Call | object.new CouchbaseClient([ url ] [, username ] [, password ])
|
---|---|
Asynchronous | no |
Description | Create a connection to Couchbase Server with given parameters, such as node URL. The connection obtains the cluster configuration from the first host to which it has connected. Further communication operates directly with each node in the cluster as required. |
Returns | (none) |
Arguments | |
string url | URL for Couchbase Server Instance, or node. |
string username | Username for Couchbase bucket. |
string password | Password for Couchbase bucket. |
The easiest way to specify a connection, or a pool of connections is to provide
it in the App.config
file of your.Net project. By doing so, you can change the
connection information without having to recompile. You can update App.config
in Visual Studio as follows:
<servers bucket="private" bucketPassword="private">
<add uri="http://10.0.0.33:8091/pools"/>
<add uri="http://10.0.0.34:8091/pools"/>
</servers>
You should change the URI above to point at your server by replacing 10.0.0.33
with the IP address or hostname of your Couchbase server machine. Be sure you
set your bucket name and password. You can also set the connection to use the
default bucket, by setting the bucket attribute to default
and leaving the
bucketPassword
attribute empty. In this case we have configured the server
with a bucket named ‘private’ and with a password ‘private.’
Connections that you create with the.Net SDK are also thread-safe objects; for persisted connections, you can use a connection pool which contains multiple connection objects. You should create only a single static instance of a Couchbase client per bucket, in accordance with.Net framework. The persistent client will maintain connection pools per server node. For more information, see MSDN: AppDomain Class.
The Couchbase.NET Client Library store operations set information within the Couchbase database. These are distinct from the update operations in that the key does not have to exist within the Couchbase database before being stored.
The Store() methods add or replace a value in the database with the specified key.
The behavior of Store
and ExecuteStore
operations is defined by setting the
first parameter to a value from the StoreMode
enumeration.
StoreMode.Add
- Add a key to the database, failing if the key exists
StoreMode.Replace
- Replace a key in the database, failing if the key does not
exist
StoreMode.Set
- Add a key to the database, replacing the key if it already
exists
JavaScript can store numbers up to a maximum size of 253. If you are storing 64-bit integers within Couchbase and want to use the numbers through the MapReduce engine, store numbers larger than 253 as a string to prevent number rounding errors.
API Call | object.Store(storemode, key, value)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | Boolean ( Boolean (true/false) ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
The Store()
method is used to persist new values by key. Any class decorated
with the Serializable
attribute may be stored.
client.Store(StoreMode.Add, "beer", new Beer() {
Brewer = "Thomas Hooker Brewing Company",
Name = "American Ale"
});
API Call | object.Store(storemode, key, value, validfor)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | Boolean ( Boolean (true/false) ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
TimeSpan validfor | Expiry time (in seconds) for key |
client.Store(StoreMode.Set, "beer", new Beer() {
Brewer = "Peak Organic Brewing Company",
Name = "IPA"
}, TimeSpan.FromSeconds(60));
API Call | object.Store(storemode, key, value, expiresat)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | Boolean ( Boolean (true/false) ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
DateTime expiresat | Explicit expiry time for key |
client.Store(StoreMode.Replace, "beer", new Beer() {
Brewer = "Six Point Craft Ales",
Name = "Righteous Rye"
}, DateTime.Now.Addhours(1));
API Call | object.ExecuteStore(storemode, key, value)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
The ExecuteStore()
methods are similar to the Store
methods, but return an
instance of an IStoreOperationResult
.
var result = client.ExecuteStore(StoreMode.Add, "beer", new Beer() {
Brewer = "Thomas Hooker Brewing Company",
Name = "American Ale"
});
if (! result.Success)
{
Console.WriteLine("Store failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteStore(storemode, key, value, validfor)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
TimeSpan validfor | Expiry time (in seconds) for key |
client.ExecuteStore(StoreMode.Set, "beer", new Beer() {
Brewer = "Peak Organic Brewing Company",
Name = "IPA"
}, TimeSpan.FromSeconds(60));
API Call | object.ExecuteStore(storemode, key, value, expiresat)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
DateTime expiresat | Explicit expiry time for key |
client.ExecuteStore(StoreMode.Replace, "beer", new Beer() {
Brewer = "Six Point Craft Ales",
Name = "Righteous Rye"
}, DateTime.Now.Addhours(1));
API Call | object.ExecuteStore(storemode, key, value)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
The ExecuteStore()
methods may define persistence or replciation (durability)
requirements. These operations will either return success only when the
durability requirements have been met. The operation fails if it times out
before meeting the durability requirement.
When specifying a persistence requirement, the persistTo parameter is set to a
value from the PersistTo
enumeration. These values specify the number of nodes
to which a key must be persisted to disk. PersistTo.One
- Require master only
persistence
PersistTo.Two, PersistTo.Three, PersistTo.Four
- Persist to master, plus one,
two or three replicas
When specifying a replication requirement, the replicateTo parameter is set to a
value from the ReplicateTo
enumeration. These values specify the number of
nodes to which a key must be replicated. ReplicateTo.One, ReplicateTo.Two,
ReplicateTo.Three
- Replicate to one, two or three replicas
//master persistence, replicate to two replicas
var result = client.ExecuteStore(StoreMode.Add, "beer", new Beer() {
Brewer = "Thomas Hooker Brewing Company",
Name = "American Ale"
}, PersistTo.One, ReplicateTo.Two);
if (! result.Success)
{
Console.WriteLine("Store failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteStore(storemode, key, value, validfor)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
TimeSpan validfor | Expiry time (in seconds) for key |
//master only persistence
client.ExecuteStore(StoreMode.Set, "beer", new Beer() {
Brewer = "Peak Organic Brewing Company",
Name = "IPA"
}, TimeSpan.FromSeconds(60), PersistTo.One, ReplicateTo.Zero););
API Call | object.ExecuteStore(storemode, key, value, expiresat)
|
---|---|
Asynchronous | no |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
DateTime expiresat | Explicit expiry time for key |
//no persistence requirement, replicate to two nodes
client.ExecuteStore(StoreMode.Replace, "beer", new Beer() {
Brewer = "Six Point Craft Ales",
Name = "Righteous Rye"
}, DateTime.Now.Addhours(1), PersistTo.Zero, ReplicateTo.Two););
The retrieve operations get information from the Couchbase database. A summary of the available API calls is listed below.
The Get() methods allow for direct access to a given key/value pair.
API Call | object.Get(key)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Object ( Binary object ) |
Arguments | |
string key | Document ID used to identify the value |
The generic form of the Get
method allows for retrieval without the need to
cast. If the stored type cannot be serialized to the generic type provided, an
InvalidCastException
will be thrown.
var beer = client.Get<Beer>("beer");
API Call | object.Get(key, expiry)
|
---|---|
Asynchronous | no |
Description | Get a value and update the expiration time for a given key |
Returns | (none) |
Arguments | |
string key | Document ID used to identify the value |
object expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Calling the Get()
method with a key and a new expiration value will cause get
and touch operations to be performed.
var val = client.Get("beer", DateTime.Now.AddMinutes(5));
API Call | object.Get(keyarray)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Object ( Binary object ) |
Arguments | |
List |
Array of keys used to reference one or more values. |
Calling Get()
with multiple keys returns a dictionary with the associated
values.
client.Store(StoreMode.Set, "brewer", "Cottrell Brewing Co.");
client.Store(StoreMode.Set, "beer", "Old Yankee Ale");
var dict = client.Get(new string[] { "brewery", "beer" });
Console.WriteLine(dict["brewery"]);
Console.WriteLine(dict["beer"]);
API Call | object.Get(key)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Object ( Binary object ) |
Arguments | |
string key | Document ID used to identify the value |
GetWithCas
returns a CasResult
, which includes the document and its CAS
value.
For example:
var casResult = client.GetWithCas("beer");
client.Store(StoreMode.Set, "beer", "some other beer", casResult.Cas)
Calling GetWithCas()
with multiple keys returns a dictionary with the
associated values. The generic version will return the Result
as an instance
of T
.
var casResult = client.GetWithCas<Beer>("beer");
Console.WriteLine(casResult.Name);
API Call | object.ExecuteGet(key)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | IGetOperationResult ( Get operation result ) |
Arguments | |
string key | Document ID used to identify the value |
The ExecuteGet and generic ExecuteGetIGetOperationResult
.
var result = client.ExecuteGet("beer");
if (! result.Success)
{
Console.WriteLine("Get failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
var beer = result.Value as Beer;
beer.Brewery = "Cambridge Brewing Company";
var casResult = client.ExecuteCas(StoreMode.Set, "beer", beer, result.Cas); //ExecuteGet returns the CAS for the key
The generic form of ExecuteGet<T>
will set the Value
property to type T
var result = client.ExecuteGet<Beer>("beer");
var beer = result.Value; //no need to cast beer as a Beer
API Call | object.ExecuteGet(key, expiry)
|
---|---|
Asynchronous | no |
Description | Get a value and update the expiration time for a given key |
Returns | IGetOperationResult ( Get operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Calling the ExecuteGet()
method with a key and a new expiration value will
cause get and touch operations to be performed.
var val = client.Get("beer", DateTime.Now.AddMinutes(5));
API Call | object.ExecuteGet(keyarray)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | IGetOperationResult ( Get operation result ) |
Arguments | |
List |
Array of keys used to reference one or more values. |
Calling ExecuteGet()
with multiple keys returns a dictionary with the
associated IGetOperationResult
values.
client.Store(StoreMode.Set, "brewery", "Cottrell Brewing Co.");
client.Store(StoreMode.Set, "beer", "Old Yankee Ale");
var dict = client.ExecuteGet(new string[] { "brewery", "beer" });
foreach(var key in dict.Keys)
{
Console.WriteLine(dict[key]);
}
GetWithLock()
prevents a key from being updated for 15 seconds by default or by setting it to a value with a maximum of 30 seconds, which defined by the server.
var casResult = client.GetWithLock("beer", TimeSpan.FromSeconds(10));
var result = client.Store(StoreMode.Set, "beer", new Beer()); //result will be false
ExecuteGetWithLock()
provides similar behavior to GetWithLock
, but returns
an IGetOperationResult
, which allows for status codes to be checked.
var lockResult = client.ExecuteGetWithLock("key", TimeSpan.FromSeconds(30));
var storeResult = client.ExecuteStore(StoreMode.Set, "key", "new value");
//storeResult.Success will be false
//storeResult.StatusCode will be equal to (int)StatusCodeEnums.DataExistsForKey
var getLockResultA = client.ExecuteGetWithLock("key");
var getLockResultB = client.ExecuteGetWithLock("key");
//getLockResultB.StatusCode will be equalt to (int)CouchbaseStatusCodeEnums.LockError)
API Call | object.KeyExists(key)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Boolean ( Boolean (true/false) ) |
Arguments | |
string key | Document ID used to identify the value |
KeyExists()
checks whether a key has been either a) written to RAM or b)
written to disk on the master node for a key. If the key exists, the value is
not returned.
var result = client.KeyExists("foo"); //result is true when "foo" exists
The update methods support different methods of updating and changing existing information within Couchbase. A list of the available methods is listed below.
The Append()
methods allow you to add information to an existing key/value
pair in the database. You can use this to add information to a string or other
data after the existing data.
The Append()
methods append raw serialized data on to the end of the existing
data in the key. If you have previously stored a serialized object into
Couchbase and then use Append, the content of the serialized object will not be
extended. For example, adding an List
of integers into the database, and then
using Append()
to add another integer will result in the key referring to a
serialized version of the list, immediately followed by a serialized version of
the integer. It will not contain an updated list with the new integer appended
to it. De-serialization of objects that have had data appended may result in
data corruption.
API Call | object.Append(key, value)
|
---|---|
Asynchronous | no |
Description | Append a value to an existing key |
Returns | Object ( Binary object ) |
Arguments | |
string key | Document ID used to identify the value |
object value | Value to be stored |
The Append()
method appends information to the end of an existing key/value
pair.
The sample below demonstrates how to create a csv string by appending new values.
client.Store(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
client.Append("beers", new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
client.Append("beers", new ArraySegment<byte>(stringToBytes(",Witte")));
You can check if the Append operation succeeded by checking the return value.
var result = client.Append("beers", new ArraySegment<byte>(stringToBytes(",Hennepin")));
if (result) {
Console.WriteLine("Append succeeded");
} else {
Console.WriteLine("Append failed");
}
API Call | object.Append(key, casunique, value)
|
---|---|
Asynchronous | no |
Description | Append a value to an existing key |
Returns | Object ( Binary object ) |
Arguments | |
string key | Document ID used to identify the value |
ulong casunique | Unique value used to verify a key/value combination |
object value | Value to be stored |
The Append
operation may also be used with a CAS value.
var storeResult = client.ExecuteStore(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
client.Append("beers", storeResult.Cas, new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
API Call | object.ExecuteAppend(key, value)
|
---|---|
Asynchronous | no |
Description | Append a value to an existing key |
Returns | IConcatOperationResult ( Concat operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object value | Value to be stored |
The ExecuteAppend operation is used to get an instance of an
IConcatOperationResult
.
client.ExecuteStore(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
var result = client.ExecuteAppend("beers", new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
if (! result.)
{
Console.WriteLine("Append failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteAppend(key, casunique, value)
|
---|---|
Asynchronous | no |
Description | Append a value to an existing key |
Returns | IConcatOperationResult ( Concat operation result ) |
Arguments | |
string key | Document ID used to identify the value |
ulong casunique | Unique value used to verify a key/value combination |
object value | Value to be stored |
The ExecuteAppend operation may also be used with a CAS value.
var storeResult = client.ExecuteStore(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
var appendResult = client.ExecuteAppend("beers", storeResult.Cas, new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
if (! appendResult.)
{
Console.WriteLine("Append failed with message {0} and status code {1}", appendResult.Message, appendResult.StatusCode);
if (appendResult.Exception != null)
{
throw appendResult.Exception;
}
}
The Decrement()
methods reduce the value of a given key if the corresponding
value can be parsed to an integer value. These operations are provided at a
protocol level to eliminate the need to get, update, and reset a simple integer
value in the database. All the.NET Client Library methods support the use of an
explicit offset value that will be used to reduce the stored value in the
database. Note that the Decrement
methods may not be used with keys that were
first created with a Store
method. To initialize a counter, you must first use
either Increment
or Decrement
with a default value.
API Call | object.Decrement(key, defaultvalue, offset)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | ulong the result of the value decremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist.
client.Remove("inventory"); //reset the counter
client.Decrement("inventory", 100, 1); //counter will be 100
client.Decrement("inventory", 100, 1); //counter will be 99
API Call | object.Decrement(key, defaultvalue, offset, validfor)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | ulong the result of the value decremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds.
client.Decrement("inventory", 100, 1, TimeSpan.FromSeconds(60));
API Call | object.Decrement(key, defaultvalue, offset, expiresat)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | ulong the result of the value decremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes.
client.Decrement("inventory", 100, 1, DateTime.Now.AddMinutes(5));
API Call | object.Decrement(key, defaultvalue, offset, casunique)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | ulong the result of the value decremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
ulong casunique | Unique value used to verify a key/value combination |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.Decrement("inventory", 100, 1, result.Cas);
API Call | object.Decrement(key, defaultvalue, offset, validfor, casunique)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | ulong the result of the value decremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
ulong casunique | Unique value used to verify a key/value combination |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.Decrement("inventory", 100, 1, TimeSpan.FromSeconds(60), result.Cas);
API Call | object.Decrement(key, defaultvalue, offset, expiresat, casunique)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | ulong the result of the value decremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
ulong casunique | Unique value used to verify a key/value combination |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.Decrement("inventory", 100, 1, DateTime.Now.AddMinutes(5), result.Cas);
API Call | object.ExecuteDecrement(key, defaultvalue, offset)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
ExecuteDecrement will return an instance of an IMutateOperationResult
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist.
client.Remove("inventory"); //reset the counter
client.ExecuteDecrement("inventory", 100, 1); //counter will be 100
var result = client.ExecuteDecrement("inventory", 100, 1); //counter will be 99
if (! result.Success)
{
Console.WriteLine("Decrement failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteDecrement(key, defaultvalue, offset, validfor)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds.
client.ExecuteDecrement("inventory", 100, 1, TimeSpan.FromSeconds(60));
API Call | object.ExecuteDecrement(key, defaultvalue, offset, expiresat)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes.
client.ExecuteDecrement("inventory", 100, 1, DateTime.Now.AddMinutes(5));
API Call | object.ExecuteDecrement(key, defaultvalue, offset, casunique)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
ulong casunique | Unique value used to verify a key/value combination |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.ExecuteDecrement("inventory", 100, 1, result.Cas);
API Call | object.ExecuteDecrement(key, defaultvalue, offset, validfor, casunique)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
ulong casunique | Unique value used to verify a key/value combination |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.ExecuteDecrement("inventory", 100, 1, TimeSpan.FromSeconds(60), result.Cas);
API Call | object.ExecuteDecrement(key, defaultvalue, offset, expiresat, casunique)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
ulong casunique | Unique value used to verify a key/value combination |
Decrement the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.ExecuteDecrement("inventory", 100, 1, DateTime.Now.AddMinutes(5), result.Cas);
The Remove()
method deletes an item in the database with the specified key.
API Call | object.Remove(key)
|
---|---|
Asynchronous | no |
Description | Delete a key/value |
Returns | Object ; supported values: |
COUCHBASE_ETMPFAIL
|
|
COUCHBASE_KEY_ENOENT
|
|
COUCHBASE_NOT_MY_VBUCKET
|
|
COUCHBASE_NOT_STORED
|
|
docid
|
|
Arguments | |
string key | Document ID used to identify the value |
Remove the item with a specified key
client.Remove("badkey");
API Call | object.ExecuteRemove(key)
|
---|---|
Asynchronous | no |
Description | Delete a key/value |
Returns | IRemoveOperationResult ( Remove operation result ) |
Arguments | |
string key | Document ID used to identify the value |
ExecuteRemove
removes an item by key and returns an instance of an
IRemoveOperationResult
var result = client.ExecuteRemove("badkey");
if (! result.Succes)
{
Console.WriteLine("Remove failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteRemove-cas(key, casunique)
|
---|---|
Asynchronous | no |
Description | Delete a key/value |
Returns | IRemoveOperationResult ( Remove operation result ) |
Arguments | |
string key | Document ID used to identify the value |
ulong casunique | Unique value used to verify a key/value combination |
ExecuteRemove
removes an item by key using a CAS operation and returns an
instance of an IRemoveOperationResult
var result = client.ExecuteRemove("badkey", 86753091234);
if (! result.Succes)
{
Console.WriteLine("Remove failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteRemove(key)
|
---|---|
Asynchronous | no |
Description | Delete a key/value |
Returns | IRemoveOperationResult ( Remove operation result ) |
Arguments | |
string key | Document ID used to identify the value |
The ExecuteRemove()
method may define persistence requirements. This operation
will return success only when the key has been removed from the specified number
of nodes.
PersistTo.One
will ensure removal from the key’s master node and not consider
removal of its replicas. A common use of this option is to combine it with a
non-stale view query, to guarantee that a deleted key is not returned in a view
result.
var result = client.ExecuteRemove("city_NC_Raleigh", PersistTo.One);
if (! result.Succes)
{
Console.WriteLine("Remove failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
else
{
var view = client.GetView("cities", "by_name").Stale(StaleMode.False);
//safe to iterate this view, because the key has been removed from disk
//and the index was updated.
}
The Increment()
methods increase the value of a given key if the corresponding
value can be parsed to an integer value. These operations are provided at a
protocol level to eliminate the need to get, update, and reset a simple integer
value in the database. All the.NET Client Library methods support the use of an
explicit offset value that will be used to reduce the stored value in the
database. Note that the Increment
methods may not be used with keys that were
first created with a Store
method. To initialize a counter, you must first use
either Increment
or Increment
with a default value.
API Call | object.Increment(key, defaultvalue, offset)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | ulong the result of the value incremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist.
client.Remove("inventory"); //reset the counter
client.Increment("inventory", 100, 1); //counter will be 100
client.Increment("inventory", 100, 1); //counter will be 101
API Call | object.Increment(key, defaultvalue, offset, validfor)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | ulong the result of the value incremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds.
client.Increment("inventory", 100, 1, TimeSpan.FromSeconds(60));
API Call | object.Increment(key, defaultvalue, offset, expiresat)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | ulong the result of the value incremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes.
client.Increment("inventory", 100, 1, DateTime.Now.AddMinutes(5));
API Call | object.Increment(key, defaultvalue, offset, casunique)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | ulong the result of the value incremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
ulong casunique | Unique value used to verify a key/value combination |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.Increment("inventory", 100, 1, result.Cas);
API Call | object.Increment(key, defaultvalue, offset, validfor, casunique)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | ulong the result of the value incremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
ulong casunique | Unique value used to verify a key/value combination |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.Increment("inventory", 100, 1, TimeSpan.FromSeconds(60), result.Cas);
API Call | object.Increment(key, defaultvalue, offset, expiresat, casunique)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | ulong the result of the value incremented by the offset (if the default value has not been stored, it will be the default value) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
ulong casunique | Unique value used to verify a key/value combination |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.Increment("inventory", 100, 1, DateTime.Now.AddMinutes(5), result.Cas);
API Call | object.ExecuteIncrement(key, defaultvalue, offset)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
ExecuteIncrement will return an instance of an IMutateOperationResult
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist.
client.Remove("inventory"); //reset the counter
client.ExecuteIncrement("inventory", 100, 1); //counter will be 100
var result = client.ExecuteIncrement("inventory", 100, 1); //counter will be 101
if (! result.Success)
{
Console.WriteLine("Increment failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecuteIncrement(key, defaultvalue, offset, validfor)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds.
client.ExecuteIncrement("inventory", 100, 1, TimeSpan.FromSeconds(60));
API Call | object.ExecuteIncrement(key, defaultvalue, offset, expiresat)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes.
client.ExecuteIncrement("inventory", 100, 1, DateTime.Now.AddMinutes(5));
API Call | object.ExecuteIncrement(key, defaultvalue, offset, casunique)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
ulong casunique | Unique value used to verify a key/value combination |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.ExecuteIncrement("inventory", 100, 1, result.Cas);
API Call | object.ExecuteIncrement(key, defaultvalue, offset, validfor, casunique)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
TimeSpan validfor | Expiry time (in seconds) for key |
ulong casunique | Unique value used to verify a key/value combination |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 60 seconds. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.ExecuteIncrement("inventory", 100, 1, TimeSpan.FromSeconds(60), result.Cas);
API Call | object.ExecuteIncrement(key, defaultvalue, offset, expiresat, casunique)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | IMutateOperationResult ( Mutate operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object defaultvalue | Value to be stored if key does not already exist |
Integer offset | Integer offset value to increment/decrement (default 1) |
DateTime expiresat | Explicit expiry time for key |
ulong casunique | Unique value used to verify a key/value combination |
Increment the inventory counter by 1, defaulting to 100 if the key doesn’t exist and set an expiry of 5 minutes. Fail if CAS value doesn’t match CAS value on server.
var result = client.ExecuteGet("inventory");
client.ExecuteIncrement("inventory", 100, 1, DateTime.Now.AddMinutes(5), result.Cas);
The Prepend()
methods allow you to add information to an existing key/value
pair in the database. You can use this to add information to a string or other
data after the existing data.
The Prepend()
methods prepend raw serialized data on to the end of the
existing data in the key. If you have previously stored a serialized object into
Couchbase and then use Prepend, the content of the serialized object will not be
extended. For example, adding an List
of integers into the database, and then
using Prepend()
to add another integer will result in the key referring to a
serialized version of the list, immediately followed by a serialized version of
the integer. It will not contain an updated list with the new integer prepended
to it. De-serialization of objects that have had data prepended may result in
data corruption.
API Call | object.Prepend(key, value)
|
---|---|
Asynchronous | no |
Description | Prepend a value to an existing key |
Returns | Object ( Binary object ) |
Arguments | |
string key | Document ID used to identify the value |
object value | Value to be stored |
The Prepend()
method prepends information to the end of an existing key/value
pair.
The sample below demonstrates how to create a csv string by prepending new values.
client.Store(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
client.Prepend("beers", new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
client.Prepend("beers", new ArraySegment<byte>(stringToBytes(",Witte")));
You can check if the Prepend operation succeeded by checking the return value.
var result = client.Prepend("beers", new ArraySegment<byte>(stringToBytes(",Hennepin")));
if (result) {
Console.WriteLine("Prepend succeeded");
} else {
Console.WriteLine("Prepend failed");
}
API Call | object.Prepend(key, casunique, value)
|
---|---|
Asynchronous | no |
Description | Prepend a value to an existing key |
Returns | Object ( Binary object ) |
Arguments | |
string key | Document ID used to identify the value |
ulong casunique | Unique value used to verify a key/value combination |
object value | Value to be stored |
The Prepend
operation may also be used with a CAS value.
var storeResult = client.ExecuteStore(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
client.Prepend("beers", storeResult.Cas, new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
API Call | object.ExecutePrepend(key, value)
|
---|---|
Asynchronous | no |
Description | Prepend a value to an existing key |
Returns | IConcatOperationResult ( Concat operation result ) |
Arguments | |
string key | Document ID used to identify the value |
object value | Value to be stored |
The ExecutePrepend operation is used to get an instance of an
IConcatOperationResult
.
client.ExecuteStore(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
var result = client.ExecutePrepend("beers", new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
if (! result.)
{
Console.WriteLine("Prepend failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
API Call | object.ExecutePrepend(key, casunique, value)
|
---|---|
Asynchronous | no |
Description | Prepend a value to an existing key |
Returns | IConcatOperationResult ( Concat operation result ) |
Arguments | |
string key | Document ID used to identify the value |
ulong casunique | Unique value used to verify a key/value combination |
object value | Value to be stored |
The ExecutePrepend operation may also be used with a CAS value.
var storeResult = client.ExecuteStore(StoreMode.Set, "beers", "Abbey Ale");
Func<string, byte[]> stringToBytes = (s) => Encoding.Default.GetBytes(s);
var prependResult = client.ExecutePrepend("beers", storeResult.Cas, new ArraySegment<byte>(stringToBytes(",Three Philosophers")));
if (! prependResult.)
{
Console.WriteLine("Prepend failed with message {0} and status code {1}", prependResult.Message, prependResult.StatusCode);
if (prependResult.Exception != null)
{
throw prependResult.Exception;
}
}
The Touch()
methods allow you to update the expiration time on a given key.
This can be useful for situations where you want to prevent an item from
expiring without resetting the associated value. For example, for a session
database you might want to keep the session alive in the database each time the
user accesses a web page without explicitly updating the session value, keeping
the user’s session active and available.
API Call | object.Touch(key, expiry)
|
---|---|
Asynchronous | no |
Description | Update the expiry time of an item |
Returns | Boolean ( Boolean (true/false) ) |
Arguments | |
string key | Document ID used to identify the value |
object expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
The Touch
method provides a simple key/expiry call to update the expiry time
on a given key. For example, to update the expiry time on a session for another
60 seconds:
client.Touch("session", TimeSpan.FromSeconds(60));
To update the expiry time on the session for another day:
client.Touch("session", DateTime.Now.AddDays(1));
The check-and-set methods provide a mechanism for updating information only if the client knows the check (CAS) value. This can be used to prevent clients from updating values in the database that may have changed since the client obtained the value. Methods for storing and updating information support a CAS method that allows you to ensure that the client is updating the version of the data that the client retrieved.
The check value is in the form of a 64-bit integer which is updated every time the value is modified, even if the update of the value does not modify the binary data. Attempting to set or update a key/value pair where the CAS value does not match the value stored on the server will fail.
The behavior of Cas
and ExecuteCas
operations is defined by setting the
first parameter to a value from the StoreMode
enumeration. StoreMode.Add
-
Add a key to the database, failing if the key exists
StoreMode.Replace
- Replace a key in the database, failing if the key does not
exist
StoreMode.Set
- Add a key to the database, replacing the key if it already
exists
API Call | object.Cas(storemode, key, value)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | CasResult<bool> ( Cas result of bool ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
The Cas()
method is used to persist new values by key. Any class decorated
with the Serializable
attribute may be stored. The CAS value is returned by
way of a CasResult
var casResult = client.Cas(StoreMode.Add, "beer", new Beer() {
Brewer = "Thomas Hooker Brewing Company",
Name = "American Ale"
});
if (casResult.Result)
{
Console.WriteLine("Cas: ", casResult.Cas);
}
API Call | object.Cas(storemode, key, value, validfor, casunique)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | CasResult<bool> ( Cas result of bool ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
TimeSpan validfor | Expiry time (in seconds) for key |
ulong casunique | Unique value used to verify a key/value combination |
client.Cas(StoreMode.Set, "beer", new Beer() {
Brewer = "Peak Organic Brewing Company",
Name = "IPA"
}, TimeSpan.FromSeconds(60));
API Call | object.Cas(storemode, key, value, expiresat, casunique)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | CasResult<bool> ( Cas result of bool ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
DateTime expiresat | Explicit expiry time for key |
ulong casunique | Unique value used to verify a key/value combination |
client.Cas(StoreMode.Replace, "beer", new Beer() {
Brewer = "Six Point Craft Ales",
Name = "Righteous Rye"
}, DateTime.Now.Addhours(1));
API Call | object.ExecuteCas(storemode, key, value)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
The ExecuteCas()
methods are similar to the Cas
methods, but return an
instance of an IStoreOperationResult
.
var result = client.ExecuteCas(StoreMode.Add, "beer", new Beer() {
Brewer = "Thomas Hooker Brewing Company",
Name = "American Ale"
});
if (! result.Success)
{
Console.WriteLine("Store failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
client.ExecuteCas(StoreMode.Replace"beer", new Beer() {
Brewer = "Thomas Hooker Brewing Co.",
Name = "American Ale"
}, result.Cas);
API Call | object.ExecuteCas(storemode, key, value, validfor, casunique)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
TimeSpan validfor | Expiry time (in seconds) for key |
ulong casunique | Unique value used to verify a key/value combination |
client.ExecuteCas(StoreMode.Set, "beer", new Beer() {
Brewer = "Peak Organic Brewing Company",
Name = "IPA"
}, TimeSpan.FromSeconds(60));
API Call | object.ExecuteCas(storemode, key, value, expiresat, casunique)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | IStoreOperationResult ( Store operation result ) |
Arguments | |
StoreMode storemode | Storage mode for a given key/value pair |
string key | Document ID used to identify the value |
object value | Value to be stored |
DateTime expiresat | Explicit expiry time for key |
ulong casunique | Unique value used to verify a key/value combination |
client.ExecuteCas(StoreMode.Replace, "beer", new Beer() {
Brewer = "Six Point Craft Ales",
Name = "Righteous Rye"
}, DateTime.Now.Addhours(1));
Provides support for views. For more information about using views for indexing and querying from Couchbase Server, here are some useful resources:
For more information on Views, how they operate, and how to write effective map/reduce queries, see Couchbase Server 2.0: Views and Couchbase Sever 2.0: Writing Views.
Sample Patterns: to see examples and patterns you can use for views, see Couchbase Views, Sample Patterns.
Timestamp Pattern: many developers frequently ask about extracting information based on date or time. To find out more, see Couchbase Views, Sample Patterns.
View Interface
GetView(designName, viewName)
GetView
takes a
designName
Design document name.
viewName
View name.
There is also a generic version of GetView, which returns view items that are stongly typed.
GetData<T>(designName, viewName)
Both versions of GetView
return an IView, which implements IEnumerable.
Therefore, when you query for the items in a view, you can iterate over the
returned collection as folows:
var beersByNameAndABV = client.GetView<Beer>("beers", "by_name_and_abv");
foreach(var beer in beersByNameAndABV) {
Console.WriteLine(beer.Name);
}
or the non-generic version:
var beersByNameAndABV = client.GetView("beers", "by_name_and_abv");
foreach(var row in beersByNameAndABV) {
Console.WriteLine((row.GetItem() as Beer).Name);
}
As you can see, when you iterate over a strongly typed view each item is of the type you specified. If you use the non-generic version, each item you enumerate over will be of type IViewRow. IViewRow provides methods for accessing details of the row that are not present when using strongly typed views.
To get the original document from the datastore:
row.GetItem();
To get a Dictionary
representation of the row:
row.Info;
To get the original document’s ID (key):
row.ItemId;
To get the key emitted by the map function:
row.ViewKey;
Before iterating over the view results, you can modify the query that is sent to the server by using the fluent methods of IView. Refer to the sample document and view below when exploring the IView API.
//map function
function(doc) {
if (doc.type == "beer") {
emit([doc.name, doc.abv], doc);
}
}
//sample json document
{
"_id" : "beer_harpoon_ipa",
"name" : "Harpoon IPA",
"brewery" : "brewery_harpoon",
"abv" : 5.9
}
To find beers with names starting with “H” and an ABV of at least 5:
var beersByNameAndABV = client.GetView("beers", "by_name_and_abv")
.StartKey(new object[] { "H", 5 });
To limit the number of beers returned by the query to 10:
var beersByNameAndABV = client.GetView("beers", "by_name_and_abv")
.Limit(10);
To group the results (when using _count for example):
var beersByNameAndABV = client.GetView("breweries", "breweries_by_state")
.Group(true);
To disallow stale results in the view:
var beersByNameAndABV = client.GetView("beers", "by_name_and_abv")
.Stale(StaleMode.False);
IView API methods may be chained. To limit the number of results to 5 and order the results descending:
var beersByNameAndABV = client.GetView("beers", "by_name_and_abv")
.Limit(5).Descending(true);
The following sections provide details about working with the IOperationResult
interface and the error codes it returns.
The standard CouchbaseClient
CRUD operations return Boolean
values. When
exceptions or failures occur, they are swallowed and false
is returned.
While there might be scenarios where the cause of a failure is not important
(for example, nonpersistent cache), sometimes you might want to access detailed error information. To that end, CouchbaseClient
provides a set of complementary ExecuteXXX methods, where XXX is the name of a standard CRUD operation. The following example shows both forms of the method for the Get
operation:
var success = client.Get("foo"); //returns a Boolean
var result = client.ExecuteGet("foo"); //returns an IOperationResult
All ExecuteXXX methods return an instance of an implementation of the
IOperationResult
interface.:
public interface IOperationResult
{
bool Success { get; set; }
string Message { get; set; }
Exception Exception { get; set; }
int? StatusCode { get; set; }
IOperationResult InnerResult { get; set; }
}
For each of the ExecuteXXX methods, a typical use pattern is to interrogate the possible error values on failure, as shown in the following example:
var result = client.ExecuteStore(StoreMode.Add, "foo", "bar");
if (! result.Success)
{
Console.WriteLine("Store operation failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
The Message
property will contain details as to why an operation failed. The
message might be from the server or it could be from the client. The
StatusCode
property is a Nullable<int>
that will be populated from the
server’s response.
Note that the 1.1 release of the OperationResult
API wrapped lower level
(networking) exceptions in an InnerResult
object. Since release 1.2,
InnerResult
is no longer populated. The property still remains so as to be
backwards compatible.
Like the standard CRUD methods, the Execute methods will swallow exceptions.
However, caught exceptions are passed back to the caller by way of the
Exception
property. This allows the caller to check for an exception and throw
it if exception behavior is desired.
There are several interfaces that extend IOperationResult
to provide
additional properties. Two important interfaces are the
INullableOperationResult<T>
and ICasOperationResult
public interface INullableOperationResult<T> : IOperationResult
{
bool HasValue { get; }
T Value { get; set; }
}
public interface ICasOperationResult : IOperationResult
{
ulong Cas { get; set; }
}
The INullableOperationResult<T>
interface is extended by the
IGetOperationResult
, providing Get operations with properties to get the
retrieved value.
var result = client.ExecuteGet("foo");
if (! result.Success)
{
Console.WriteLine("Get operation failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.Exception != null)
{
throw result.Exception;
}
}
if (result.HasValue)
{
var value = result.Value;
}
else if (result.StatusCode == 1)
{
Console.WriteLine("Key does not exist");
}
IGetOperationResult
also extends ICasOperationResult
. Therefore, it is
possible to get the CAS value from an ExecuteGet operation. The snippet below
demonstrates the generic ExecuteGet
method.
var result = client.ExecuteGet<City>("CT_Hartford");
if (result.Success)
{
var city = result.Value;
city.Population = 124775;
client.ExecuteStore(StoreMode.Set, "CT_Hartford", city, result.Cas); //will fail if Cas is not the same value on server
}
ExecuteStore
methods return IStoreOperationResult
instances.
var result = client.ExecuteStore(StoreMode.Add, "foo", "bar");
if (! result.Success)
{
Console.WriteLine("Get operation failed with message {0} and status code {1}", result.Message, result.StatusCode);
if (result.StatusCode == 2)
{
Console.WriteLine("Key already exists");
}
if (result.Exception != null)
{
throw result.Exception;
}
}
IStoreOperationResult
also extends ICasOperationResult
. Therefore, it is
possible to get the CAS value from an ExecuteStore operation.
var city = new City() { Name = "Hartford", State = "CT" };
var result = client.ExecuteStore(StoreMode.Add, "CT_Hartford", city);
if (result.Success)
{
city.Population = 124775;
client.ExecuteStore(StoreMode.Set, "CT_Hartford", city, result.Cas); //will fail if Cas is not the same value on server
}
The IMutateOperationResult
, IConcatOperationResult
interfaces also return
the CAS value. These interfaces are returned by
ExecuteIncrement/ExecuteDecrement
and ExecuteAppend/ExecutePrepend
respectively. IRemoveOperationResult
includes only the properties found in
IOperationResult
.
For more information on which API methods support ExecuteXXX variants, see the API reference.
The Couchbase .NET Client uses the following types of CRUD methods:
Methods that return the primitive value of the result (for example,bool
or integer
) . The signature of these methods matches the operation’s name (for example, Increment(..)).
Methods that return an IOperationResult
object. The signature of these methods begins with the prefix “Execute” (for example, ExecuteIncrement).
The benefit of using the methods that return IOperationResult
is that they give you additional information of about the result of the operation that enables you to handle specific error or failure cases or to determine what caused the error to occur. The most important fields in the IOperationResult
object are:
The following table lists the enumeration name and corresponding numerical value (in hexadecimal), origin of the value, and description of each StatusCode
:
Enumeration | Value | Origin | Description |
---|---|---|---|
Success | 0x0 | Server | Operation was successful |
KeyNotFound | 0x1 | Server | Key was not found on server |
KeyExists | 0x2 | Server | Key already exists on server |
ValueToLarge | 0x3 | Server | Value is to large |
InvalidArguments | 0x4 | Server | The operations arguments are invalid |
ItemNotStored | 0x5 | Server | The item could not be stored |
IncrDecrOnNonNumericValue | 0x6 | Server | An attempt was made to increment or decrement a non-numeric value e.g. a string |
VBucketBelongsToAnotherServer | 0x7 | Server | The vBucket the key is mapped to has been changed. Common during rebalance scenarios and operation should be retried |
AuthenticationError | 0x20 | Server | SASL authentication has failed. Check the password or user name of the server or bucket |
AuthenticationContinue | 0x21 | Server | Used during SASL authentication |
InvalidRange | 0x22 | Server | Invalid range was specified |
UnknownCommand | 0x81 | Server | Operation was not recognized by server. Should never occur with a Couchbase supported client |
OutOfMemory | 0x82 | Server | Server is out of memory. This is usually temporary, but should prompt further investigation |
NotSupported | 0x83 | Server | A client attempted an operation that was not supported by the server |
InternalError | 0x84 | Server | Server error state |
Busy | 0x85 | Server | Server is temporarily too busy. This may warrant a retry attempt |
TemporaryFailure | 0x86 | Server | |
SocketPoolTimeout | 0x91 | Client | A timeout has occurred while attempting to retrieve a connection. This can happen during rebalance scenarios or during times of high throughput on the client. A retry attempt is warranted in this case |
UnableToLocateNode | 0x92 | Client | Usually a temporary state of the client during rebalance/failover scenarios when a configuration change has occurred (server added or removed from cluster for example). A retry attempt is warranted in this case |
NodeShutdown | 0x93 | Client | Temporary client state during a configuration change when an operation is using the older state of the cluster. A retry attempt is warranted in this case |
OperationTimeout | 0x94 | Client | The 1.X client uses synchronous IO, If a connection is terminated by the server a timeout will occur after n seconds on the client if the current operation does not complete. A retry attempt is warranted in this case |
You can find the StatusCode
enumeration within Enyim.Caching
assembly: Enyim.Caching.StatusCode
.
The following sections provide details on how to use the CouchbaseClient
JSON
extensions.
The Couchbase.Extensions
namespace contains a series of extension methods that
provide out of the box support for storing and retrieving JSON with Couchbase
Server. These methods use JSON.NET, and provide only limited customization. If
your JSON serialization needs are advanced, you will need to provide your own
methods to work with JSON.
To use the JSON extensions in your application, you’ll first need to include a using block for the extensions namespace
using Couchbase.Extensions;
Once that using directive has been added, an instance of an ICouchbaseClient
will be able to execute various JSON methods.
Generally speaking, for each store or get method, there is a corresponding Json method.
//Person will be stored using binary serialization
var storeResult = client.ExecuteStore(StoreMode.Set, "person1", new Person { Name = "John" });
//Person will be stored as a view-ready, JSON string { name : "John" }
var storeJsonResult = client.ExecuteStoreJson(StoreMode.Set, "person2", new Person { Name = "John" });
//getResult.Value will contain an instance of a Person
var getResult = client.ExecuteGet<Person>("person1");
//InvalidCastException will be thrown, since "person1" contains a binary value
var getJsonResult = client.ExecuteGetJson<Person>("person1");
//Invalid cast exception, because person2 contains a string
var getResult = client.ExecuteGet<Person>("person2");
//getJsonResult.Value will contain an instance of a Person
var getJsonResult = client.ExecuteGetJson<Person>("person2");
ExecuteStoreJson
is overloaded to provide support for key expiration, and
durability requirements.
var person = new Person { Name = "John" };
//key will expire in 10 minutes
var storeJsonResult1 = client.ExecuteStoreJson(StoreMode.Set, "john", person, DateTime.Now.AddMinutes(10));
//key will expire in 10 minutes
var storeJsonResult2 = client.ExecuteStoreJson(StoreMode.Set, "john", person, TimeSpan.FromMinutes(10));
//storeJsonResult3 will fail unless the key is written to disk on its master node, and replicated to two nodes
var storeJsonResult3 = client.ExecuteStoreJson(StoreMode.Set, "john", person, PersistTo.One, ReplicateTo.Two);
CAS operations are also available via the ExecuteCasJson
extension method.
var person = new Person { Name = "John" };
var getResult = client.ExecuteGetJson("key");
getResult.Value.Name = "John Z";
//would fail if the CAS value has changed on the server since it was retrieved
var casResult = client.ExecuteCasJson(StoreMode.Set, "key", getResult.Value, getResult.Cas);
The JSON extensions make an important assumption about your model objects, and how they serialize and deserialize them. It is important that you understand this assumption before using these methods. Consider the following Person class:
public class Person
{
public string Id { get; set; }
public string Type { get { return "person"; } }
public string Name { get; set; }
}
The assumption is that the Id
property of a domain object is meant to be
mapped to the key in the key/value used to store your data.
var person = new Person { Id = "person_12345", Name = "Tony Soprano" };
var storeResult = client.ExecuteStoreJson(StoreMode.Set, person.Id, person);
//the JSON would be:
//{ "name" : "Tony Soprano", type : "person" }
When the JSON is saved, it will ignore the Id
property and its value. Since it
is assumed that the Id
is the key, it would be redundant to include it in the
saved JSON. Moreover, it would also be a challenge to keep the key in sync with
the JSON.
When the Person
is retrieved, the ExecuteGetJson
method will automatically
map the Id
property to the key. This mapping is achieved by inserting the key
as an “id” property in the JSON document before it is deserialized.
var getResult = client.ExecuteGetJson<Person>("person_12345");
Console.WriteLine("Id: " + Person.Id);
//Id: person_12345
The importance of how the “id” property is mapped is that you are able to take
advantage of JSON.NET attributes to have some control over which property is
your “Id” property. If you wanted to map the key to a Key
property, then you
could use the JsonProperty
attribute as below. Note however, that should you
use this mapping, it will override the ignore “id” on write and you’ll have your
key in the JSON.
public class Person
{
[JsonProperty("Id")]
public string Key { get; set; }
public string Type { get { return "person"; } }
public string Name { get; set; }
}
The default behavior of the JSON extensions works well with strongly typed
views, which will also map an Id
property of a class to its key as you iterate
over the view.
var view = client.GetView<Person>("people", "by_name", true).Key("Tony Soprano");
var person = view.FirstOrDefault();
Console.WriteLine("Id: " + Person.Id);
//Id: person_12345
The following sections provide details on the App|Web.config configuration options for the .NET Client Library
The CouchbaseClientSection
class is the configuration section handler.
<section name="couchbase" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase"/>
The minimum configuration options are to include a couchbase
section with a
servers
element with at least one URI, which is used to bootstrap the client.
At least two node URIs should be provided, so that in the event that the client
can’t reach the first, it will try the second.
bucket
(default) The bucket against which the client instance will perform
operations
bucketPassword
The password for authenticated buckets.
username
The username used to secure a cluster.
password
The password associated with the cluster username
retryCount
The number of times to retry a failed attempt to read cluster
config
retryTimeout
(00:00:02) The amount of time to wait in between failed attempts
to read cluster config
observeTimeout
(00:01:00) The amount of time to wait for persistence or
replication checks when using ExecuteStore
or ExecuteRemove
with durability
requirements
httpRequestTimeout
(00:01:00) The amount of time to wait for the HTTP
streaming connection to receive cluster configuration
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
</couchbase>
The “bucket” and “bucketPassword” attributes of the servers
element default to
“default” and an empty string respectively.
<couchbase>
<servers bucket="default" bucketPassword="H0p$">
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
</couchbase>
The client may also be configured in code.
var config = new CouchbaseClientConfiguration();
config.Urls.Add(new Uri("http://localhost:8091/pools/"));
config.Bucket = "default";
var client = new CouchbaseClient(config);
The socketPool
element is used to configure the behavior of the client as it
connects to the Couchbase cluster. It uses the following attributes (defaults are in parentheses):
minPoolSize
(10) The minimum number of connections in the connection pool
maxPoolSize
(20) The maximum number of connections in the connection pool
connectionTimeout
(00:00:10) The amount of time the client is waiting to a)
establish a connection to the memcached server, b) get a free connection from
the pool. If it times out the operation will fail. (And return false or null,
depending on the operation.)
deadTimeout
(00:00:10) When all pool urls are unavailable the client will
check the first one again after deadTimeout
time elapses. Additionally, the
client has a basic dead node detection mechanism which will also use this
timeout to reconnect servers which went offline.
queueTimeout
(00:00:02.500) This is the time that a worker thread will wait for
a connection to become available when the connection pool is empty. This would
happen in a high-throughput scenario or if the operations take longer than expected.
The default is 2500 milliseconds.
receiveTimeout
(00:00:10) This amount of time that the client will wait on a
connection during a read. The default is 10 seconds.
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<socketPool minPoolSize="10" maxPoolSize="20" />
</couchbase>
The client will periodically check the health of its connection to the cluster
by performing a heartbeat check. By default, this test is done every 10 seconds
against the bootstrap URI defined in the servers
element.
uri
(defaults to first server Uri from servers
element) The Uri used for the
heartbeat check
interval
(10000ms) Frequency with which heartbeat check executes
enabled
(true) Enables or disables heartbeat check.
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<heartbeatMonitor uri="http://127.0.0.1:8091/pools/heartbeat" interval="60000" enabled="true" />
</couchbase>
When executing view queries, the client will make requests over HTTP. That
connection may be managed using the httpClient
element.
initializeConnection
(true) When true, the ServicePointManager
is
initialized asynchronously on client creation rather than on the first view
request
timeout
(00:01:15) How long to wait for a view request before timing out
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<httpClient initializeConnection="false" timeout="00:00:45"/>
</couchbase>
When executing view queries, HTTP requests are made by IHttpClient instances
which are created by factories. The factory is defined in the
httpClientFactory
element.
type
(Couchbase.RestSharpHttpClientFactory, Couchbase) The fully qualified
type name of an IHttpClientFactory
implementation,
RestSharpHttpClientFactory
is the default. HammockHttpClientFactory
is also
supported, but has known issues.<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<httpClientFactory type="Couchbase.RestSharpHttpClientFactory, Couchbase" />
</couchbase>
When executing view queries, the design document is toggled between dev mode
(prefixed by dev_) and production mode by setting the documentNameTransformer
element.
type
(Couchbase.Configuration.ProductionModeNameTransformer, Couchbase) The
fully qualified type name of an INameTransformer
implementation<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<documentNameTransformer type="Couchbase.Configuration.ProductionModeNameTransformer, Couchbase" />
</couchbase>
The keyTransformer
is used to normalize/validate the item keys before sending
them to the server.
type
(Enyim.Caching.Memcached.DefaultKeyTransformer) must be the fully
qualified name of a type implementing IMemcachedKeyTransformer
factory
must be the fully qualified name of a type implementing
IProviderFactory<IMemcachedKeyTransformer>
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<keyTransformer type="Enyim.Caching.Memcached.DefaultKeyTransformer, Enyim.Caching" />
</couchbase>
The transcoder
is used to serialize stored/retrieved objects.
type
(Enyim.Caching.Memcached.DefaultKeyTransformer) must be the fully
qualified name of a Type implementing ITranscoder
factory
must be the fully qualified name of a type implementing
IProviderFactory<ITranscoder>
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<keyTransformer type="Enyim.Caching.Memcached.DefaultTranscoder, Enyim.Caching" />
</couchbase>
The transcoder
is used to map objects to servers in the pool.
type
(Enyim.Caching.Memcached.DefaultKeyTransformer) must be the fully
qualified name of a type implementing IMemcachedNodeLocator
factory
must be the fully qualified name of a type implementing
IProviderFactory<IMemcachedNodeLocator>
<couchbase>
<servers>
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
<keyTransformer type="Enyim.Caching.Memcached.DefaultNodeLocator, Enyim.Caching" />
</couchbase>
It is not possible to configure (in app|web.config) a single instance of a CouchbaseClient to work with multiple buckets. Though it is possible to programmatically reconstruct a client to work with multiple buckets, it is not recommended. The process of creating a client is expensive (relative to other Couchbase operations) and should ideally be done once per app domain.
It is possible however to set multiple config sections in app|web.config to allow for multiple client instances to be created, while still maintaining bucket affinity.
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="couchbase">
<section name="bucket-a" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase"/>
<section name="bucket-b" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase"/>
</sectionGroup>
</configSections>
<couchbase>
<bucket-a>
<servers bucket="default">
<add uri="http://127.0.0.1:8091/pools" />
</servers>
</bucket-a>
<bucket-b>
<servers bucket="beernique" bucketPassword="b33rs">
<add uri="http://127.0.0.1:8091/pools" />
</servers>
</bucket-b>
</couchbase>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
After defining the config sections, bucket specific clients are created by reading the appropriate config sections and passing the config section reference to the constructor of the CouchbaseClient. Again, constructing the client should not be done per operation, but rather per app domain.
var bucketASection = (CouchbaseClientSection)ConfigurationManager.GetSection("couchbase/bucket-a");
var bucketBSection = (CouchbaseClientSection)ConfigurationManager.GetSection("couchbase/bucket-b");
var clientA = new CouchbaseClient(bucketASection);
var clientB = new CouchbaseClient(bucketBSection);
clientA.ExecuteStore(StoreMode.Set, "fooA", "barA");
var itemA = clientA.Get<string>("fooA");
Console.WriteLine(itemA);
clientB.ExecuteStore(StoreMode.Set, "fooB", "barB");
var itemB = clientB.Get<string>("fooB");
Console.WriteLine(itemB);
The following sections provide details on how to enable logging for the .NET Client Library
To enable logging, you can tap into the logging capabilities provided by the Enyim.Caching dependency. Enyim logging currently supports either log4net or NLog.
Start by adding a reference to either Enyim.Caching.Log4NetAdapter or Enyim.Caching.NLogAdapter. Both are available as part of the part of the client library zip file, or as separate NuGet packages.
To install via NuGet, look for either the CouchbaseLog4NetAdapter or CouchbaseNLogAdapter package.
You could also get the projects from Github. If you use these Visual Studio projects, you’ll need NuGet installed, as dependencies to NLog and log4Net are managed using NuGet.
For log4net, your configuration should include an enyim.com section that defines which log factory to use along with standard log4net configuration.
The log4net configuration will vary by the type of appender you are using. For more information on log4net configuration, see http://logging.apache.org/log4net/release/manual/configuration.html.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="enyim.com">
<section name="log" type="Enyim.Caching.Configuration.LoggerSection, Enyim.Caching" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<enyim.com>
<log factory="Enyim.Caching.Log4NetFactory, Enyim.Caching.Log4NetAdapter" />
</enyim.com>
<log4net debug="false">
<appender name="LogFileAppender" type="log4net.Appender.FileAppender,log4net">
<param name="File" value="c:\\temp\\error-log.txt" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" />
</layout>
</appender>
<root>
<priority value="ALL" />
<level value="DEBUG" />
<appender-ref ref="LogFileAppender" />
</root>
</log4net>
</configuration>
You’ll also need to initialize (only once in your app) log4net in your code with the standard log4net initializer.
log4net.Config.XmlConfigurator.Configure();
NLog configuration requires setting the log factory to NLogAdapter and including the appropriate NLog configuration section.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="enyim.com">
<section name="log" type="Enyim.Caching.Configuration.LoggerSection, Enyim.Caching" />
</sectionGroup>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
<enyim.com>
<log factory="Enyim.Caching.NLogFactory, Enyim.Caching.NLogAdapter" />
</enyim.com>
<nlog>
<targets>
<target name="logfile" type="File" fileName="c:\temp\error-log.txt" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile" />
</rules>
</nlog>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
</configuration>
See https://github.com/nlog/nlog/wiki for more NLog configuration details.
The following sections provide details about using the Couchbase .NET Client to manage buckets and design documents.
Cluster management is performed by using methods of the CouchbaseCluster
class, which implements the ICouchbaseCluster
interface, both of which are in
the Couchbase.Management
namespace.
using Couchbase.Management;
The CouchbaseCluster
is configured using the same config definitions (code or
XML) used to create instances of a CouchbaseClient
. When managing the cluster
with the .NET client, the cluster username and password must be provided.
<couchbase>
<servers username="Administrator" password="qwerty" >
<add uri="http://127.0.0.1:8091/pools"/>
</servers>
</couchbase>
The default constructor for CouchbaseCluster
looks for a section named
“couchbase”. It is possible to use a named section, as follows:
var config = ConfigurationManager.GetSection("anothersection") as CouchbaseClientSection;
var cluster = new CouchbaseCluster(_config);
To configure CouchbaseCluster
in code, pass an instance of an
ICouchbaseClientConfiguration
to the constructor.
var config = new CouchbaseClientConfiguration();
config.Urls.Add(new Uri("http://localhost:8091/pools/"));
config.Username = "Administrator";
config.Password = "qwerty";
var cluster = new CouchbaseCluster(config);
To get a list of buckets from the server, there are two methods. Bucket[]
ListBuckets()
returns an array of Bucket instances, one for each bucket in the
cluster
bool TryListBuckets(out Bucket[] buckets)
returns true when no errors and
provides the list of buckets as an out param. When errors occur, returns false
and a null buckets array
To get a single of bucket from the server, there are two methods. Bucket
GetBucket(string bucketName)
returns a Bucket instance for a bucket with the
given name
bool TryGetBucket(string bucketName, out Bucket bucket)
returns true when no
errors and provides the named bucket as an out param. When errors occur, returns
false and a null bucket
To get the count of items in a bucket, or across buckets use the following
methods: long GetItemCount(string bucketName)
returns the count of items in a
bucket with the given name
long GetItemCount()
returns the count of items in all buckets in a cluster
To get the count of items in a bucket, or across buckets use the following
methods: long GetItemCount(string bucketName)
returns the count of items in a
bucket with the given name
long GetItemCount()
returns the count of items in all buckets in a cluster
To manage the buckets in a cluster, there are three methods. void
CreateBucket(Bucket bucket)
create a bucket with the given bucket properties
//create an authenticated Couchbase bucket
cluster.CreateBucket(
new Bucket
{
Name = "newBucket",
AuthType = AuthTypes.Sasl,
BucketType = BucketTypes.Membase,
Quota = new Quota { RAM = 100 },
ReplicaNumber = ReplicaNumbers.Zero
}
);
//create an unauthenticated Couchbase bucket
cluster.CreateBucket(
new Bucket
{
Name = "newBucket",
AuthType = AuthTypes.None,
BucketType = BucketTypes.Membase,
Quota = new Quota { RAM = 100 },
ProxyPort = 9090,
ReplicaNumber = ReplicaNumbers.Two
}
);
//create a memcached bucket
cluster.CreateBucket(
new Bucket
{
Name = "newBucket",
AuthType = AuthTypes.None,
BucketType = BucketTypes.Memcached,
Quota = new Quota { RAM = 100 },
ProxyPort = 9090,
ReplicaNumber = ReplicaNumbers.Zero
}
);
void UpdateBucket(Bucket bucket)
recreates an existing bucket, updating only
changed parameters.
cluster.UpdateBucket(
new Bucket
{
Name = "newBucket",
Quota = new Quota { RAM = 128 },
ProxyPort = 9090,
AuthType = AuthTypes.None
}
);
void DeleteBucket(string bucketName)
deletes an existing bucket
cluster.DeleteBucket("bucketName");
To remove the data (but not design documents) from a bucket, use the
FlushBucket
method. void FlushBucket(string bucketName)
flushes data from a
bucket
cluster.FlushBucket("bucketName");
There are four methods for managing design documents with the
CouchbaseCluster
. bool CreateDesignDocument(string bucket, string name,
string document)
creates a design document on the server, using the provided
JSON string as the source of the document.
//create a production mode design document
var json =
@"{
""views"": {
""by_name"": {
""map"": ""function (doc) { if (doc.type == ""city"") { emit(doc.name, null); } }""
}
}
}";
var result = cluster.CreateDesignDocument("default", "cities", json);
//create the same view using development mode
var devResult = custer.CreateDesignDocument("default", "dev_cities", json);
bool CreateDesignDocument(string bucket, string name, Stream source)
create a
design document on the server using a readable Stream
instance as the source
of the document.
var stream = new FileStream("Data\CityViews.json", FileMode.Open);
var result = cluster.CreateDesignDocument("default", "cities", stream);
string RetrieveDesignDocument(string bucket, string name)
Retreive a design
document from a bucket
var document = cluster.RetrieveDesignDocument("default", "cities");
bool DeleteDesignDocument(string bucket, string name)
Deletes a design
document from a bucket
var result = cluster.DeleteDesignDocument("default", "cities");
This section provides troubleshooting tips.
When you bulk load data to Couchbase Server, you can accidentally overwhelm available memory in the Couchbase cluster before it can store data on disk. If this happens, Couchbase Server will immediately send a response indicating the operation cannot be handled at the moment but can be handled later.
This is sometimes referred to as “handling Temp OOM”, where where OOM means out of memory. Note though that the actual temporary failure could be sent back for reasons other than OOM. However, temporary OOM is the most common underlying cause for this error.
To handle this problem, you could perform an exponential backoff as part of your bulk load. The backoff essentially reduces the number of requests sent to Couchbase Server as it receives OOM errors:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Couchbase;
using Couchbase.Configuration;
using Enyim.Caching.Memcached.Results;
using Enyim.Caching.Memcached;
using System.Threading;
namespace BulkLoader
{
public class StoreHandler
{
CouchbaseClient _cbc;
public StoreHandler(IList<Uri> uris, string bucketName, string bucketPassword)
{
var config = new CouchbaseClientConfiguration();
foreach (var uri in uris)
{
config.Urls.Add(uri);
}
config.Bucket = bucketName;
config.BucketPassword = bucketPassword;
_cbc = new CouchbaseClient(config);
}
/// Perform a regular Store with storeMode.Set
public IStoreOperationResult Set(string key, object value)
{
return _cbc.ExecuteStore(StoreMode.Set, key, value);
}
/// Continuously try a set with exponential backoff until number of tries or
/// successful. The exponential backoff will wait a maximum of 1 second, or whatever
public IStoreOperationResult Set(string key, object value, int tries)
{
var backoffExp = 0;
var tryAgain = false;
IStoreOperationResult result = null;
try
{
do
{
if (backoffExp > tries)
{
throw new ApplicationException("Could not perform a set after " + tries + " tries.");
}
result = _cbc.ExecuteStore(StoreMode.Set, key, value);
if (result.Success) break;
if (backoffExp > 0)
{
var backOffMillis = Math.Pow(2, backoffExp);
backOffMillis = Math.Min(1000, backOffMillis); //1 sec max
Thread.Sleep((int)backOffMillis);
Console.WriteLine("Backing off, tries so far: " + backoffExp);
}
backoffExp++;
if (! result.Success)
{
var message = result.InnerResult != null ? result.InnerResult.Message : result.Message;
Console.WriteLine("Failed with status: " + message);
}
//Future versions of the .NET client will flatten the results and make checking for
//InnerResult objects unnecessary
tryAgain = (result.Message != null && result.Message.Contains("Temporary failure") ||
result.InnerResult != null && result.InnerResult.Message.Contains("Temporary failure"));
} while (tryAgain);
}
catch (Exception ex)
{
Console.WriteLine("Interrupted while trying to set. Exception:" + ex.Message);
}
// note that other failure cases fall through. status.isSuccess() can be
// checked for success or failure or the message can be retrieved.
return result;
}
}
}
The following sections provide release notes for individual release versions of Couchbase Client Library .NET. To browse or submit new issues, see Couchbase Client Library .NET Issues Tracker.
NCBC-585: Refactor finalization logic so that Exceptions are not thrown on finalizer thread
NCBC-805: .NET SDK 1.3.10 can cause long delays on GC Finalizer thread processing
NCBC-655: Minimize time in lock to reduce contention
This patch minimizes the amount of time held on a lock during a rebalance scenario which reduces pausing that may be observed at that time.
NCBC-691: Maximum and Default Locking times are misleading
The maximum lock in 2.5.1 and 3.0.0 is 30 secs, the default is 15 seconds. Any attempt to set a lock higher than the maximum results in the default being used. This patch sets the default locktime to 15 seconds, which was previously TimeSpan.Zero. The code already throws a AOOR exception if the locktime is longer than 30 seconds.
Changes documentation to reflect correct name of dll.
NCBC-647: Markdown Links not Rendered
Changes documentation so that links that were not being displayed, are now displayed.
NCBC-643: TimeSpan expiration under 1 sec results in an infinite timeout
Fixes a bug in the client in which an expiration of less than one second would round to zero. The server would interpret this as infinity and the key would never be evicated. This is the opposite of what the user intended.
NCBC-621: IRI Parsing error when bootstrapping on mono
IRI parsing is required for versions of the server >= 3.0. Mono does not currectly support IRI parsing, so on Mono the code would fail when it attempted to enable it. This patch effectivly ignores IRI parsiong on mono.
NCBC-616: Fixed PagedView with only one page
When using a PageView with a page size larger than the number of items in the result set, the MoveNext() operation indicates there are no items in the current page and returns false. This fix ensures that if the number of items is less than the page size, the enumeration will still happen.
NCBC-614: Review and reduce verbosity of logging in INFO mode
This fix replaces some of the INFO logging with DEBUG level logging to minimize log files sizes.
NCBC-583: Ensure Dispose is called on every MD5CryptoServiceProvider class usage
Every Get or Set call invokes KetamaNodeLocator.Locate(string key). The memcached keys are hashed through the MD5 algorithm to find the node they are supposed to be stored on. The current implementation creates a new MD5CryptoServiceProvider class, which has an underlying SafeHashHandle that registers into the finalizer queue. Because the code does not call Dispose on the MD5CryptoServiceProvider instance, the SafeHashHandle remains in the finalizer queue until the garbage collector disposes of it properly.
NCBC-577: Enable IriParsing for supporting Views w/Couchbase Server 3.0
This fixes a breaking change in how Couchbase Server 3.0 handles view queries by associating a UUID with a bucket and how System.Uri handles unicode encoding across different versions of the .NET Common Language Runtime (CLR). Users running under .NET Framework 4.5 and later are unaffected. Users who want to run Couchbase Server 3.0 with CLR version 4.0 or earlier must either use version 1.3.8 of the Couchbase .NET SDK or provide the following elements in their App.Config or Web.Config to enable IriParsing:
<uri>
<iriParsing enabled="true"/>
</uri>
NCBC-564: Provide PDB Files with Binaries
PDB files are now included with binaries on S3.
NCBC-564: Respect JsonSerializer settings for deserialization.
This patch allows you to override the default JsonSerializer settings during deserialization.
NCBC-555: Multi-get can fail and return a null StatusCode.
This patch ensures that the correct StatusCode is returned when a multi-get operation fails.
NCBC-500: IsArrayOrCollection does not support IEnumerable
This patch fixes a bug where the IsArrayOrCollection
method would return false for types supporting IEnumerable
.
NCBC-513: Use Timespan.TotalSeconds instead of Timespan.Seconds
This patch fixes a bug that was introduced in 1.3.6 by the NCBC-485 patch. The Timespan.TotalSeconds
property should be used in
place of Timespan.Seconds
.
NCBC-503: CouchbaseCluster construction is missing un/pw
This patch fixes a bug where the user name and password was not being included in the REST call to the API. This would cause the API to return a 401 Unauthorized even if the password and user name were included within object creation.
NCBC-457: Change verbosity of HB logging to DEBUG
This patch changes the verbosity of the logging within the heartbeat component from INFO to DEBUG.
NCBC-485: Small TTL values can get lost by client/server clock-drift
This patch fixes a bug where differences between the server clock and the client clock would cause small time spans to provide inconsistent and unexpected results.
NCBC-499: Refactor InternalPoolImpl so that NRE is not thrown
This patch provides a more defensive Dispose method implementation so that when multiple threads try to dispose on the pool, only one thread will perform the dispose. It also protects against null reference exceptions by checking for nullity of internal references before calling their dispose methods.
NCBC-494: Finalizer throws NRE if Pools has already been finalized/GC’d
Fixes a race condition where the internal CouchbasePool
may have already been garbage collected before the finalizer is run on the parent object,
causing a NullReferenceException
(NRE) to be thrown.
NCBC-438: Add finalizer to CouchbaseClient to ensure resources are released
Add a destructor to CouchbaseClient that disposes the SocketPool. This gives reliable socket tear down for applications cleanly terminating.
NCBC-425: SetSocketOption throws exception under mono runtime
This fixes the bug that threw the exception and makes the LingerOptions
an
optional configuration by adding lingerEnabled
and lingerTime
options to the
socketPool configuration in the app.config
. The socket uses the
default linger options (as defined by the IP stack) if lingerEnabled
is
false or not set in the configuration. If lingerEnabled
is true and the
lingerTime
value is specified as 0, then no linger time is used.
If lingerEnabled
is true and lingerTime
is greater than 0, the
linger time is set to the value specified in the lingerTime
option.
You can change these values
in the App.Config:
<socketPool lingerEnabled=true lingerTime=00:00:10/>
For information about linger options from Microsoft, see LingerOption class.
DeleteBucket() should be called before checking that the bucket is deleted and is no longer listed.
NCBC-415: Fixes regression bug in which wrong GetView overload was called
This is the same fix as NCBC-425 but adds unit tests.
NCBC-416: Method overloading ambiguity with named parameters
This is a fix for a regression bug where the method overloading was
differentiated with return type only; hence the wrong method was invoked from
public IView GetView(string designName, string viewName)
. This caused
the rows to be null when iterating over a view with no output other than
its keys.
NCBC-399: Standardize to spaces for indenting code in source files
Replaced all tabs (\t) with four spaces per the Visual Studio (VS) standard
NCBC-261: Use an enumeration instead of integers for return codes
This patch adds an extension method to Enyim.Caching.StatusCodeExtensions
to make it easier to work with
the IOperationResult.Status
property.
NCBC-408: Retry Node Check during View Operations
During a View operation, the client slook for a node to use to execute the operation against. In certain cases, such as a rebalance, the list of nodes might be empty. This adds an additional check if a candidate node is not found before the client gives up and returns a “View {0} was mapped to a dead node, failing.” error.
NCBC-407: Retry View Operations When Specific Error Cases are Encountered
This patch enables the client to retry view operations when specific error cases are encountered. For example, all errors in the 300 range are retried and in certain cases 404 and 500’s are retried as well.
NCBC-398: Support Json.NET >= 6.0.1
This commit removes the restriction on Json.NET version and bumps the build version from Nuget Version 4.5.11 to 5.0.8 (assembly versions are slightly different). 5.0.8 is the last released version before 6.0.1.
NCBC-388: Test case improvement
This commit adds a key and then verifies that Store()
fails if the key is added again.
NCBC-394: Add ‘Warn’ Level Logging to ‘core’ configuration classes
Serializes the client configuration and writes it to the log appender when the log level is set to INFO.
NCBC-396: Log the creation of client instances
This commit logs the creation of a client instance, writing the current configuration to the log when log level INFO is configured. The purpose is to make it easier to isolate issues specific to a client’s configuration.
NCBC-395: Assign each client instance an identifier
This commit adds an Identity property to the CouchbaseClient class that makes it easier to isolate a specific client’s log messages in environments where multiple clients are writing to the same log file.
**Note: Version 1.3.2 was shipped as 1.3.3**
Ensure that Nuget installs Newtonsoft.NET <=5.0.8
If this is not specified in the .nuspec file, Nuget will attempt to
install the lastest version 6.0.0 which is not compatible with view
calls using 1.3.2 1.3.3 version of the Couchbase .NET SDK. These incompatible
issues will be resolved in a future release, likely 1.3.3 1.3.4.
NCBC-380: Filtering on Compound Key with ‘+’ Char Fails
This fix adds methods for enabling URL encoding of HTTP request parameters when making view requests. This allows for queries against keys that contain special characters by ensuring that they are properly encoded.
NCBC-360: Default connection timeout improperly set in SocketPoolConfiguration.cs
This fixes a bug where the default connection timeout property was set incorrectly in the 1.3.0 release.
NCBC-357: Set TCP keep-alives on Connections
Enables TCP keep-alives on socket connections.
NCBC-358: Sort nodes to reduce number server config changes
When a server configuration update is signaled, the GetHashCode()
method compares the new configuration and the previous configuration to determine whether it needs to recreate the connection pool because the cluster has changed on the server. In some cases the only change is to the order of the nodes. This fix orders the node list before doing the comparison so that only significant changes result in a connection pool reconfiguration.
NCBC-368: Randomize Bootstrap Node
Randomizes the selection of the bootstrap node so that all clients do not use the same node, which can cause “stampeding herd” and lopsided resource utilization in large clusters or when a cluster supports a large number of clients. This ensures that bootstrapping is evenly distributed over all of the URLs in the configured bootstrap list. The first ‘good’ node is chosen as a bootstrapper.
NCBC-369: ObjectDisposedException is not handled in PerformMultiGet
Handles a case where an uncaught exception can cause the host process to fail.
NCBC-359 Allows Serializer customization
This patch allows serializer customization by placing a static field
on CouchbaseClientExtensions.JsonSerializerSettings
that enables users of
the couchbase extension class to customize the serializer.
NCBC-375: preferring IPv4 address
If you configure Couchbase Server with a host name instead of an IP address (IPv4 because you can’t enter an IPv6 address via the Couchbase Web Console), the .NET client library refuses the connection. This fix ensures that the IPv4 address is chosen over the IPv6 address when a connection is made.
NCBC-361: Refactor Unit Test Project
General improvements to the Couchbase.Tests
project. This is an ongoing effort.
NCBC-373: .NET GetJSON operation throws System.ArgumentNullException
Handle the case where a key is accessed without a value for a view.
.NET Couchbase Client 1.3.1 is largely a maintenance release and includes the following features and fixes:
NCBC-334: Add a post-merge git hook for updating the assembly version:
This commit adds a git post-merge hook that runs after a pull by our build process, assuming that the remote repo has changes. It uses git-describe to get the latest revision since the last tag and includes the current SHA1. It then updates a Version.txt file and the AssemblyInfo.cs
class with this information in the AssemblyInformationalVersion
attribute. A future enhancement will update the AssemblyVersion and AssemblyFileVersion, add the git log (release notes) for the current release as an embedded resource, and expose a public method to access it.
NCBC-327: Update Nuspec files to current VS Solution Configuration:
Previously, the projects in the solution would use NuGet (in some cases) to handle internal references instead of simple using project-to-project references. This caused various versioning issues. This commit changes the Nuspec files so that they only reference the NuGet dependencies from NuGet and not when doing local builds.
NCBC-341: AOOR when deserializing bootstrap config with empty ‘pools’ element:
During certain times, such as a rebalance, when a config contains an empty pools
element, it causes an ArgumentOutOfRangeException
to be thrown. This changes the logic to check for this case and throw a BootstrapConfigurationException
instead.
NCBC-337: Fix for ‘View vquery was mapped to a dead node, failing.’ errors:
When a config update occurs, the client might receive a configuration with nodes with a ‘status’ of ‘warmup’, in this state the ‘couchbaseApiBase’ is not returned. This puts the client in a state where a client node might not have an IHttpClient to execute the view request again, thus the request fails. This commit adds a check to ensure that the node is ‘valid’ by checking for nodes where the IHttpClient is not null.
NCBC-352: Flag all Increment/Decrement methods with CAS params as ‘Obsolete’:
Some of the Increment and Decrement methods in the Enyim libraries have parameters for CAS values, this commit flags those methods as obsolete and they may be removed in future versions of the .NET SDK. The reason that they are being flagged as obsolete is because Increment/Decrement are atomic operations on the server, thus making the CAS parameter redundant. Furthermore, testing indicates that they do not work as expected.
NCBC-353: Add node IP to error messages so that users can isolate issues easier:
This changes the format of the error message returned from the IOperationResult.Message so that it is easier to track and resolve issues with various nodes in the cluster. Property.
Note
Clients that depend on the former message format might break:
Please adjust accordingly when upgrading to this version of the .NET SDK
NCBC-345: Update Readme.mdown to reflect changes in the Couchbase .NET SDK versioning policy:
For versioning, we use the Semantic Versioning 2.0 model. All officially released binaries use the following convention for version numbers: MAJOR.MINOR.PATCH. For example, 1.2.9 is a patch release and 1.3.0 is a minor release. We occasionally provide snapshots of mid-iteration bug fixes for validation purposes. For snapshot releases we add another segment to the version number: MAJOR.MINOR.PATCH.SNAPSHOT, where SNAPSHOT is a number starting at 5000 that is incremented with each build. For example, 1.3.0.5000 is a snapshot release. Snapshot builds are not thoroughly tested or supported in any way—user beware!
NCBC-344: NotImplementedException when storing against MemcachedClient in v1.3 client:
Fixes a regression bug for people using our fork of the MemcachedClient API. Couchbase strongly recommends using the CouchbaseClient for reasons such as this.
NCBC-289: Does not return errors object on exception:
An exception is thrown when an error is detected for all view error cases. For the next version (2.x) of the client, we will make a decision on how we want the client to behave when an error is encountered when processing a view. This commit makes it consistent across all error cases and does not change the interface, which would likely impact users by requiring them to change their code to check an error property for failures.
This commit also adds additional unit tests and refactors the CouchbaseViewHandler
class so that we can pass streams that contain text resembling errors returned from the server into the ReadResponse
method.
.NET Couchbase Client 1.3.0 includes the following features and fixes:
NCBC-310: Refactor Connection Pool:
Refactor the CouchbaseNode class so it depends upon a different implementation of the internal socket pool that utilizes a queue structure instead of the stack-based implementation used by MemcachedClient. The PooledSocket now has a new interface, IPooledSocket, with CouchbaseClient and MemcachedClient having separate implementations. These changes impact only CouchbaseClient instances—MemcachedClient instances still use the older implementation.
The benefits of this include:
Method MemcachedClient.ExecuteGet(IEnumerable
NCBC-333: reference cleanup when SocketPool is Disposed
If the client is terminated without calling dispose, a NullReferenceException for the SocketPool class might be thrown. This ensures that the reference is not null before dereferencing it.
NCBC-331: Change queueTimeout default from 100ms to 2500ms
The default value for queueTimeout is currently 100 ms, which is extremely low and will unnecessarily cause queue time-out exceptions. This will increase the queueTimeout to 2.5 seconds, which is still a relatively low amount. Note that this only affects threads waiting on the SocketPool for a socket, not the actual time it takes to execute an operation.
NCBC-329: Ensure IOperationResult returns StatusCode on failure.
Many of the operations on failure do not return a StatusCode in their IOperationResult resturn values. This fixes most of these cases.
NCBC-318: Status Code list contains duplicate value
StatusCode.UnknownCommand is now 0x0081.
NCBC-324: ExecuteGet(string key, DateTime newExpiration) returns unexpected result codes
NCBC-309: Move .NET API documentation from docs repo to auto-doc
The first step in changing the client documentation involves adding XML comments to each public method. In later releases, client documentation will be generated from the XML comments.
NCBC-299: Fix project references:
Changes in the dependencies in the GitHub repository so that Couchbase.Log4NetAdapter and other projects that use NuGet do so only for 3rd party dependencies. All dependencies between Couchbase libraries are now via project references and the NuGet packages point to the latest Couchbase Client build.
NCBC-316: Allow GetJson to support the retrieval of arrays and lists:
Adds support to the CouchbaseClientExtensions.GetJson(…) methods to handle the deserialization of arrays and lists of objects and not just individual objects.
NCBC-306: .NET GetJSON operation throws null reference exception:
Add support for null values persisted for a key via the CouchbaseClientExtensions.GetJson(…) method. This method no longer throws a NullReferenceException when the value store for key is null and instead just returns null.
NCBC-293: Enhance Couchbase.Client.Multiget(..) to allow getting details on missing items:
Multiget now returns information for every operation (for example, success or failure).
Note
Due to this change, you might need to modify the way you handle the object returned by multiget. Previously, multiget only returned successful operations. In this release, both failed and successful operations are returned in the results.
NCBC-317: Mark Sync operations as obsolete:
The CouchbaseClient.Sync(…) operations have been deprecated and will not be supported. They will be removed in future releases of the .NET client. The functionality has been superseded by the CouchbaseClient.Observe(…) methods and the PersistTo and ReplicateTo parameters.
Adds XML comments to all public methods of the Couchbase.Client class. The future API documentation will be based on these XML comments.
.NET Couchbase Client 1.2.9 is a follow up release to 1.2.8 and fixes the following issue:
.NET Couchbase Client 1.2.8 release is a fixes the following issues:
.NET Couchbase Client 1.2.6 fixes an issue where the 1.2.5 NuGet package contained an unsigned Enyim.Caching assembly (3.5 only). The 4.0 NuGet package was not affected.
.NET Couchbase Client 1.2.5 removes the RestSharp and Hammock dependencies, adds support for.NET 3.5, along with new features and bug fixes.
New Features and Behavior Changes in 1.2.5
NCBC-231: Support for Unlock without CAS operation
var lockResult = client.ExecuteGetWithLock("key", TimeSpan.FromSeconds(20));
var storeResult = client.ExecuteStore(StoreMode.Set, "key", "new value");
Assert.That(storeResult.Success, Is.False);
var unlockResult = client.ExecuteUnlock("key"); //unlock before the timeout
//may also use Boolean form (e.g., var boolVal = client.Unlock("key");
Assert.That(unlockResult.Success, Is.True);
var storeResult2 = client.ExecuteStore(StoreMode.Set, "key", "another new value");
Assert.That(storeResult.Success, Is.True);
The logging assemblies are now available via separate NuGet pacakges, which reference NLog and log4net via NuGet, instead of local assemblies. See CouchbaseLog4NetAdapter and CouchbaseNLogAdapater on NuGet.
NCBC-254: JSON extensions should default to ignore Id property on add/replace/set. This change allows for compatibility with the generic view queries, which map the key to an Id property.
var thing = new Thing { Id = key, SomeProperty = "Foo", SomeOtherProperty = 17 };
var result = _Client.StoreJson(StoreMode.Set, key, thing);
Assert.That(result, Is.True);
//An "Id" property will be removed from the stored JSON
var obj = _Client.Get<string>(key);
Assert.That(obj, Is.Not.StringContaining(""id""));
//GetJson will automatically assign the key to an Id property
var savedThing = _Client.GetJson<Thing>(key);
Assert.That(savedThing.Id, Is.StringContaining(key));
NCBC-246: The.NET Client Library is now code compatible with the .NET Framework version 3.5. The NuGet package and release zip file contain both 4.0 and 3.5 assemblies. The solution (see GitHub) now includes a Couchbase .Net35 project.
NCBC-247: RestSharp and Hammock are no longer dependencies of the Couchbase.NET Client Library.
No change should be necessary, unless using explicit RestSharp or Hammock configuration for the HttpClientFactory. If not, the default configuration will use the new HttpClientFactory, which relies only on WebClient. In 1.2.4, the default HttpClientFactory relied on RestSharp.
RestSharp and Hammock will be usable via a separate NuGet project, or from the Couchbase.HttpClients project (via GitHub). These assemblies will not be signed, to avoid collisions with a custom RestSharp, which is unsigned.
Fixes in 1.2.5
NCBC-256: Throw exception when lock expiry exceeds server limit (30 seconds)
try {
var lockResult = client.ExecuteGetWithLock("key", TimeSpan.FromSeconds(40));
} catch (ArgumentOutOfRangeException ex) {
//handle exception
}
.NET Couchbase Client 1.2.4 adds support for Get with Lock feature, along with bug fixes.
New Features and Behavior Changes in 1.2.4
NCBC-238: Support for key exists check without getting value.
Observe is used behind the scenes to support the check. KeyExists
method
checks that key is either in FoundPersisted
or FoundNotPersisted
state on
master node.
var exists = client.KeyExists("foo");
NCBC-231: Support for Get with Lock
var lockResult = client.ExecuteGetWithLock("key", TimeSpan.FromSeconds(30));
var storeResult = client.ExecuteStore(StoreMode.Set, "key", "new value");
Assert.That(storeResult.Success, Is.False);
Assert.That(storeResult.StatusCode.Value, Is.EqualTo((int)StatusCodeEnums.DataExistsForKey));
//or
var getLockResultA = client.ExecuteGetWithLock("key");
var getLockResultB = client.ExecuteGetWithLock("key");
Assert.That(getLockResultB.StatusCode, Is.EqualTo((int)CouchbaseStatusCodeEnums.LockError));
Fixes in 1.2.4
NCBC-239: Fix to set RestSharpHttpClient.Timeout
property correctly. Was
previously being set to TimeSpan.Milliseconds
instead of total
TimeSpan.Milliseconds
.
NCBC-243: Fix to fail store operations when PersistTo
or ReplicateTo
durability requirements could not be satisfied by number of online nodes.
.NET Couchbase Client 1.2.3 GA addresses an invalid strong name issue with Enyim.Caching in release 1.2.2.
.NET Couchbase Client 1.2.2 GA provides stability fixes and new API methods.
New Features and Behavior Changes in 1.2.2
NCBC-229: Support Remove with CAS operation.
var getResult = client.ExecuteGet("key");
var removeResult = client.ExecuteRemove("key", getResult.Cas);
Fixes in 1.2.2
NCBC-234: Fix to include CAS on return of ExecuteGetJson extension method.
NCBC-228: Fix for concurrency issues during rebalance lead to frequent PooledSocket errors.
Known Issues in 1.2.2
.NET Couchbase Client 1.2.1 GA provides stability fixes and new API methods.
New Features and Behavior Changes in 1.2.1
NCBC-165: IView now includes a CheckExists
method, which will allow callers to
verifiy that a view exists in a design document prior to executing a view query.
This method will perform an HTTP request to get the design document contents.
var view = client.GetView("cities", "by_name");
if (view.CheckExists())
{
foreach(var item in view)
{
//do something
}
}
NCBC-179: Additional JSON extensions are now available. For each Store or
ExecuteStore method in the ICouchbaseClient
API, there is now a corresponding
JSON method. These additions include methods for CAS and expiry overloads. An
ExecuteGetJson
method has also been provided. Note these methods are
intentionally not included in the ICouchbaseClient
interface as they are
explicitly tied to Newtonsoft.Json and its default serialization rules.
//store
var city = new City { Name = "Hartford", State = "CT", Type = "city" };
var result = client.ExecuteStoreJson(StoreMode.Set, "city_Hartford_CT", city, DateTime.Now.AddMinutes(30));
//get
var otherCity = client.ExecuteGetJson<City>("city_Bridgeport_CT");
NCBC-159: Support for getting debug info from views.
var view = client.GetView("cities", "by_name").Debug(true);
view.Count(); //need to execute the query to get debug info
var local = view.DebugInfo["local"]; //DebugInfo is a dictionary
NCBC-190: CouchbaseCluster
now has method FlushBucket. Creating buckets with
flush enabled is also supported.
var cluster = new CouchbaseCluster(config);
cluster.CreateBucket(new Bucket
{
Name = "transaction",
AuthType = AuthTypes.Sasl,
BucketType = BucketTypes.Membase,
Quota = new Quota { RAM = 100 },
ReplicaNumber = ReplicaNumbers.Zero,
FlushOption = FlushOptions.Enabled
});
//and flushing
cluster.FlushBucket("transaction");
Fixes in 1.2.1
NCBC-166: The DefaultKeyTransformer
no longer forbids chars 0x00-0x20 and
space. To provide support for legacy Memcached key rules, the
LegacyMemcachedKeyTransformer
may be used.
<keyTransformer type="Enyim.Caching.Memcached.LegacyMemcachedKeyTransformer, Enyim.Caching" />
NCBC-189: Fix to NRE when ExecuteIncrement or ExecuteDecrement returned null
StatusCode
.
NCBC-195: NRE no longer thrown when client cannot locate a node on which to execute a view. The lack of available nodes is logged in the debug log and an InvalidOperationException is intentionally raised. NCBC-222: is tracking a 1.2.2 fix for an improved exception type.
NCBC-172: 1.2.0 Hammock dependency was throwing a null reference exception when executing a view query against an offline node. The 1.2.1 release replaces Hammock with RestSharp for view execution. Hammock is still supported, but RestSharp is the new default. If Hammock is explicitly configured, then Hammock will still be used for view execution. RestSharp is the suggested view REST library. To ensure RestSharp is in use, App|Web.config must not contain snippet below. If configured in code, the HttpClientFactory should not be set.
<httpClientFactory type="Couchbase.HammockHttpClientFactory, Couchbase" />
NCBC-197: When 0 bytes are received on sockets, but read was valid, Enyim client was throwing an exception with the message “?.” A descriptive exception message is now included.
NCBC-192: NRE was being thrown when executing ops against a down node. NRE was
also the symptom displayed when app client configuration was incorrect. Ops
against a bad node should now return the message “Unable to locate node” when
using the IOperationResult
methods. There is a constant for this error.
var result = client.ExecuteGet("somekey");
if (! result.Success && result.Message == ClientErrors.FAILURE_NODE_NOT_FOUND)
{
//couldn’t reach the node, check config if first run of app
}
NCBC-212: ExecuteRemove
is no longer swallowing status codes on errors.
StatusCode
property was always null previously on errors.
Known Issues in 1.2.1
Couchbase Client 1.2 GA is the first GA release to support Couchbase Server 2.0. 1.2 is backwards compatible with Couchbase Server 1.8.
In addition to support for new features of Couchbase Server 2.0, the Couchbase .NET Client Library 1.2 adds stability improvements to iteself and its dependent Enyim.Caching library.
The Couchbase .NET Client Library 1.2 requires the .NET Framework 4.0 or higher.
Fixes in 1.2.0
NCBC-168: Socket errors were previously being swallowed and did not bubble up through ExecuteXXX method return values.
NCBC-161: Run views only on nodes in cluster supporting couchApiBase (Couchbase nodes)
Known Issues in 1.2.0
NCBC-172: During a rebalance or fail over, view queries may result in an unhandled NullReferenceException. This exception is raised by a thread in the dependency Hammock.
NCBC-170: If an exception occurs before data are read, the PooledSocket may be returned to the pool marked still alive and with a dirty buffer. In some situations, a wrong magic value error may result.
NCBC-176: Flushing of buckets is not yet supported in Couchbase.Management API
New Features and Behavior Changes in 1.2.0-BETA-3
New CouchbaseCluster GetItemCount method (NCBC-92)
View timeout is now configuragble (NCBC-158)
Implemented remove with observe (NCBC-163)
ListBucket object graph now matches full server JSON (NCBC-142)
New UpdateBucket method on CouchbaseCluster (NCBC-143)
ICouchbaseClient interface completed to match CouchbaseClient public methods (NCBC-151)
Auto-map Id property to “id” field in view rows on generic view queries (NCBC-154)
Debug now supported as view parameter (NCBC-159)
Add support to build under Mono (NCBC-132)
(Experimental) support for spatial views (NCBC-47).
New CouchbaseCluster GetBucket and TryGetBucket methods to get single bucket (NCBC-72)
Fixes in 1.2.0-BETA-3
ExecuteGet no longer reports “failed to locate node” on cache misses (NCBC-130)
Don’t swallow pooled socket errors (NCBC-168)
View requests are now made to a randomly selected node from cluster (NCBC-146)
Observe reliability fixes (NCBC-129, NCBC-128, NCBC-124, NCBC-127)
Failed bootstrap node no longer puts client in invalid state (NCBC-134).
Null reference exceptions now longer (occasionally) thrown during rebalancing.
Updated Enyim submodule reference to latest commit (NCBC-167)
Pre-fetch views to cache network pools for view requests (NCBC-149)
Client now handles correctly -1 vbucket indexes in cluster config (NCBC-148)
Null reference exceptions now longer (occasionally) thrown during rebalancing (NCBC-121).
HTTP and connection timeouts are now separate (NCBC-34)
Deleted keys return null during generic view queries with non-stale iterations (NCBC-157)
Delete bucket handles 500 error from server (NCBC-119)
No longer disposing Timer in heartbeat check when it’s disabled (NCBC-136)
Known Issues in 1.2.0-BETA-3
New Features and Behavior Changes in 1.2.0-DP4
New bucket administration methods
var cluster = new CouchbaseCluster("couchbase"); //name of config section with credentials
cluster.CreateBucket(new Bucket { … });
var buckets = cluster.ListBuckets();
cluster.DeleteBucket();
New, basic JSON conversion extension methods for serializing objects to and from JSON. Methods use Newtonsoft.Json for JSON conversions.
using Couchbase.Extensions;
var result = client.StoreJson<Beer>(StoreMode.Set, "foo", new Beer { … });
var beer = client.GetJson<Beer>("foo");
1.2.0 specific configuration elements (HttpClientFactory and DocumentNameTransformer) now have defaults and 1.1 configuration will work with 1.2.0.
using Couchbase.Extensions;
var result = client.StoreJson<Beer>(StoreMode.Set, "foo", new Beer { … });
var beer = client.GetJson<Beer>("foo");
New design document administration methods
var cluster = new CouchbaseCluster("couchbase"); //name of config section with credentials
cluster.CreateDesignDocument("bucketname", "{ … }");
var designDoc = cluster.RetrieveDesignDocument("bucketname", "designdocname");
cluster.DeleteDesignDocument("bucketname", "designdocname");
Fixes in 1.2.0-DP4
New Features and Behavior Changes in 1.2.0-DP3
Initial implementation of Observe and Store with durability checks.
//check for master persistence
var result = client.ExecuteStore(StoreMode.Set, "foo", "bar", PersistTo.One);
//check for master persistence with replication to 2 nodes
var result = client.ExecuteStore(StoreMode.Set, "foo", "bar", PersistTo.One, ReplicateTo.Two);
Known Issues in 1.2.0-DP3
Fixes in 1.2.0-DP2
Generic view requests no longer emitting the original document as value. Client Get method is used instead to retrieve original document.
Reduced views no longer break from missing “id” field in row.
Paging no longer breaks.
DevelopmentModeNameTransformer now correctly prepends dev_ on view requests.
New Features and Behavior Changes in 1.2-DP
Initial support for Couchbase Server 2.0 view API.
var view = client.GetView("designdoc", "viewname");
foreach(var item in view)
{
Console.WriteLine(item.ItemId);
}
Couchbase.dll is now compiled against the .NET Framework 4.0