Search:

Search all manuals
Search this manual
Manual
Couchbase Client Library: .NET (C#) 1.2
Community Wiki and Resources
Wiki: .NET Client Library
Download Client Library
.NET Client Library
Couchbase Developer Guide 2.0
Couchbase Server Manual 2.0
SDK Forum
Additional Resources
Community Wiki
Community Forums
Couchbase SDKs
Parent Section
2 Couchbase and ASP.NET MVC
Chapter Sections
Chapters

2.7. Brewery CRUD

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