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.9. Collated Views

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>