Sample Code

The .NET SDK supports creation and handling of extended attributes.

Subdocument Operations and Extended Attributes

A high-level summary of extended attributes can be found in Extended Attributes. Extended attributes are handled by means of extensions to the Subdocument API.

.NET Extended Attributes Example

The following code demonstrates how extended attributes can be used. It assumes that Couchbase Server is established on localhost; that the Full Administrator username and password are Administrator and password respectively; and that the travel-sample bucket is installed. For information on installing the travel-sample bucket, see Sample Buckets.

using System;
using System.Collections.Generic;
using Couchbase;
using Couchbase.Configuration.Client;
using Couchbase.Core;
using Couchbase.Management;

namespace XATTRsSample
{
    class Program
    {
        static void Main()
        {
            var config = new ClientConfiguration
            {
                Servers = new List<Uri> {new Uri("http://10.112.170.101:8091/")}
            };

            // Access the cluster that is running on the local host, authenticating with
            // the username and password of the Full Administrator. This
            // provides all privileges.
            //
            using (var cluster = new Cluster(config))
            {
                Console.WriteLine("Authenticating as administrator.");
                cluster.Authenticate("Administrator", "password");

                // Open the travel-sample bucket.
                //
                var bucket = cluster.OpenBucket("travel-sample");

                // Add key-value pairs to hotel_10138, representing traveller-Ids and associated discount percentages.
                //
                bucket.MutateIn<dynamic>("hotel_10138")
                    .Upsert("discounts.jsmith123", "20", SubdocMutateFlags.CreatePath | SubdocMutateFlags.XattrPath)
                    .Upsert("discounts.pjones356", "30", SubdocMutateFlags.CreatePath | SubdocMutateFlags.XattrPath)

                    // The following lines, "insert" and "remove", simply demonstrate insertion and
                    // removal of the same path and value.
                    //
                    .Insert("discounts.jbrown789", "25", SubdocMutateFlags.CreatePath | SubdocMutateFlags.XattrPath)
                    .Remove("discounts.jbrown789", SubdocMutateFlags.XattrPath)

                    .Execute();

                // Add key-value pairs to hotel_10142, again representing traveller-Ids and associated discount percentages.
                //
                bucket.MutateIn<dynamic>("hotel_10142")
                    .Upsert("discounts.jsmith123", "15", SubdocMutateFlags.CreatePath | SubdocMutateFlags.XattrPath)
                    .Upsert("discounts.pjones356", "10", SubdocMutateFlags.CreatePath | SubdocMutateFlags.XattrPath)
                    .Execute();

                // Create a user and assign roles. This user will search for their
                // available discounts.
                //
                Console.WriteLine("Upserting new user.");

                cluster.CreateManager().UpsertUser("jsmith123", "jsmith123pwd", "John Smith",
                    new Role[]
                    {
                        // Roles required for the reading of data from
                        // the bucket.
                        //
                        new Role {Name = "data_reader", BucketName = "travel-sample"},
                        new Role {Name = "query_select", BucketName = "travel-sample"},

                        // Roles required for the writing of data into
                        // the bucket.
                        //
                        new Role {Name = "data_writer", BucketName = "travel-sample"},
                        new Role {Name = "query_insert", BucketName = "travel-sample"},
                        new Role {Name = "query_delete", BucketName = "travel-sample"},

                        // Role required for the creation of indexes
                        // on the bucket.
                        //
                        new Role {Name = "query_manage_index", BucketName = "travel-sample"}
                    }
                );
            }

            using (var cluster = new Cluster(config))
            {
                // Re-access the cluster as the created user.
                //
                Console.WriteLine("User now connecting and authenticating.");
                cluster.Authenticate("jsmith123", "jsmith123pwd");

                Console.WriteLine("Opening travel-sample bucket as user.");
                var bucket = cluster.OpenBucket("travel-sample");

                // Perform a N1QL Query to return document IDs from the bucket. These IDs will be
                // used to reference each document in turn, and check for extended attributues
                // corresponding to discounts.
                //
                Console.WriteLine("Searching for discounts, as user.");
                    var result = cluster.Query<dynamic>(
                        "SELECT id, meta(`travel-sample`).id " +
                        "AS docID FROM `travel-sample`;"
                );

                // Get the docID of each document returned, and use the ID to determine whether
                // the extended attribute exists.
                //
                var resultsReturned = string.Empty;
                var searchPath = "discounts.jsmith123";

                foreach (var row in result)
                {
                    // Get the docID of the current document.
                    //
                    var theId = (string) row.docID;

                    // Determine whether a hotel-discount has been applied to this user.
                    //
                    var whetherDiscountExistsForUser = bucket.LookupIn<dynamic>(theId)
                        .Exists(searchPath, SubdocLookupFlags.XattrPath)
                        .Execute();

                    // If so, get the discount-percentage.
                    //
                    if (whetherDiscountExistsForUser.Success)
                    {
                        var percentageValueOfDiscount = bucket.LookupIn<dynamic>(theId)
                            .Get(searchPath, SubdocLookupFlags.XattrPath)
                            .Execute();

                        // If the percentage-value is greater than 15, include the document in the
                        // results to be returned.
                        //
                        if (percentageValueOfDiscount.Content<int>(searchPath) > 15)
                        {
                            resultsReturned = resultsReturned + Environment.NewLine + bucket.Get<dynamic>(theId);
                        }
                    }
                }

                // Display the results, which features only hotels offering more than a 15% discount
                // to the current user.
                //
                Console.WriteLine("Results returned are: {0}", resultsReturned);
           }
        }
    }
}

Virtual Extended Attributes Example

Using the Sub-Document API, Virtual XATTR can be used to fetch metadata about a document, via the $document virtual XATTR. A common use case is discovering documentation expiration metadata, or TTL:

const string key = "airline_17628";

// set document expiration to 1 minute
bucket.Touch(key, TimeSpan.FromMinutes(1));

// get the ttl using the XATTR
var subdocResult = bucket.LookupIn<dynamic>(key)
    .Get("$document.exptime", SubdocPathFlags.Xattr)
    .Execute();

var ttl = subdocResult.Content<long>("$document.exptime");
Console.WriteLine($"TTl for key '{key} is {ttl}");

// update TTL to 0 (doesn't expire)
bucket.Touch(key, TimeSpan.Zero);

// retrieve new ttl plus document size and a value from the JSON
var result = bucket.LookupIn<dynamic>(key)
    .Get("$document.exptime", SubdocPathFlags.Xattr)
    .Get("$document.value_bytes", SubdocPathFlags.Xattr)
    .Get("callsign")
    .Execute();

ttl = result.Content<long>("$document.exptime");
var size = result.Content<long>("$document.value_bytes");
var callSign = result.Content<string>("callsign");
Console.WriteLine($"TTl for key '{key} is {ttl}");
Console.WriteLine($"Size for key '{key} is {size}");
Console.WriteLine($"Callsign for key '{key} is {callSign}");