Sunday, January 3, 2016

Couchbase Mobile - Part 2 - Couchbase lite views (Indexes!)

Hi,

In the part 1 we've built our tiny yet cool application, we've even replicated it to another Couchbase Lite.

But what now? We want to actually use it!
So how do we use a database? well at least with "getting" the data you have couple of options:
1) Get by primary key
2) Get by an index (or "selecting" it where x)

Up until now, in our simple sample app we could only use the "primary key" to access our data and retrieve it.

But it's not the only way to get you data from Couchbase Lite,
In this part we will learn the basics of Couchbase lite indexing. AKA Views.

On the the views, we run our Queries.
So we need to:

1. Create View
2. Run Queries on the view
3. Get the results

We will built our use case - of how using "views" in Couchbase Lite.

1. start a new WPF project.



2. Add Nuget Couchbase.Lite package


3. Copy that XAML

 <Window x:Class="CouchbaseLiteViews_Blog.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:CouchbaseLiteViews_Blog"  
     mc:Ignorable="d"  
     Title="CouchbaseLite Working with views" Height="285.808" Width="525">  
   <Grid>  
     <Grid.ColumnDefinitions>  
       <ColumnDefinition Width="100"/>  
       <ColumnDefinition Width="*"/>  
     </Grid.ColumnDefinitions>  
     <Grid.RowDefinitions>  
       <RowDefinition Height="*"/>  
       <RowDefinition Height="auto"/>  
     </Grid.RowDefinitions>  
     <StackPanel Grid.Column="0" Grid.Row="0" Margin="0 10 0 0">  
       <Button Content="Insert" Click="InsertDocumentClick" />  
       <Button Content="Read" Click="GetDocumentClick" />  
       <Button Content="InsertSomeData!" Click="InsertSomeDataClick" />  
     </StackPanel>  
     <StackPanel Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="2" Margin="0 10 0 0">  
       <TextBox Text="{Binding DocumentId}" Margin="1"/>  
       <TextBox Text="{Binding DocumentText}" TextWrapping="Wrap" AcceptsReturn="True" Height="190" VerticalScrollBarVisibility="Visible"/>   
     </StackPanel>  
     <StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" Margin="0 10 0 0" Orientation="Horizontal">  
       <Button Content="GetDocument" Width="100" Margin="1" Click="GetByCityClick"/>  
       <TextBox Text="{Binding City}" Width="100"/>  
     </StackPanel>  
   </Grid>  
 </Window>  

Which translates to
GUI generates form the XAML above

4. After you got the basic UI, which you can explore later (nothing much here really), lets go the the actual code.

After we started all up, and Initialized the Database let us define our views.
In this case I've defined 1 view - just to show how to set things up.

     private void GenerateViews()  
     {  
       var docsByCity = _database.GetView("docs_by_city");  
       docsByCity.SetMap((doc, emit) =>  
       {  
         if (doc.ContainsKey("City"))  
         {  
           emit(doc["City"], doc);  
         }  
       }, "1");  
     }  

What you can see here, that once I retrieve a name from the _database I can define a map on it,
a map is basically a projection and filtering.

In the example above, I've created a view named "docs_by_city",
assigned a delegate, checked if some key ("City") exist and then emitted it to the index.
simple as that.
We've just created our index which for every document contains a property named City - it emits the whole document, you can choose to emit whatever you want, depends on app's requirements.
It can be adjusted for better performance and smaller index size.
Also you can put as your key about any string you would like or compose your index from several properties to target special needs.
It's never good to store the entire document in the index as it basically make a copy of the document inside the index. Try to keep your index as small as possible. But if you happen to need some kind of index which has the entire document as a result, for performance it's better to keep the document in the index instead of accessing the result.Document property - to same some round tripping to the database.
The number "1" here, it the version of the index, During development if you change the map function you also need to increment that number (in case you haven't deleted the whole database), in order to rebuild the index.
There are 2 special queries.
1. Get all documents count. (with _database.DocumentCount)
2. Get all documents. (with _database.CreateAllDocumentsQuery())
After we defined our view (*index) we can start writing the code and use it.

The usage, is fairly simple only 5 steps.
  1. Get the view
  2. Create a query on the view
  3. Define your criteria on the index
  4. Run it
  5. Read it
In code it's look even simpler

     private void GetByCityClick(object sender, RoutedEventArgs e)  
     {  
       var docsByCity = _database.GetView("docs_by_city");  
       var query = docsByCity.CreateQuery();  

       query.StartKey = City;  
       query.EndKey = City;  

       var queryResults = query.Run();  
       MessageBox.Show(string.Format("{0} documents has been retrieved for that query", queryResults.Count));

       if (queryResults.Count == 0) return;  

       var documents = queryResults.Select(result => JsonConvert.SerializeObject(result.Value, Formatting.Indented)).ToArray();  
       var commaSeperaterdDocs = "[" + string.Join(",", documents) + "]";  

       DocumentText = commaSeperaterdDocs;  
     }  

I want the exact "City" so i've written on start and end key the same value.
I run the query and check if there is any results.
Then I "beautify" the result (for every value) and return that as a JSON array.
Please pay attention here that I'm not using result.Document but result.Value, as using the result.Document will not use the index and will go and query the database for each result.
so for performance, please use result.key, result.value or result.DocumentId.
Now just add that part to generate some data...

     private void InsertSomeDataClick(object sender, RoutedEventArgs e)  
     {  
       var result = MessageBox.Show("Press Yes to insert some data (10 docs)!", "Confirm", MessageBoxButton.YesNo);  
       if (result == MessageBoxResult.Yes)  
       {  
         var count = _database.DocumentCount;  
         string[] cities = { "London", "New York", "Tel Aviv" };  
         var rnd = new Random();  
         for (int i = 0; i < 10; i++)  
         {  
           var id = "document" + (i + count);  
           var cityIndex = rnd.Next(0, 3);  
   
           var properties = new Dictionary<string, string>();  
           properties.Add("name", "Roi Katz");  
           properties.Add("City", cities[cityIndex]);  
             
           var doc = JsonConvert.SerializeObject(properties);  
           InsertDocument(id, doc);  
         }   
         MessageBox.Show("10 Records inserted");  
       }  
     }  

And we are good to go!
This is how we do a simple view!
Of course we have more to come on Couchbase lite views, it's just the start.

Of course we do need to create the proper properties,
So for full project, please check my GitHub page.

Roi.





1 comment:

  1. Your blog has given me that thing which I never expect to get from all over the websites. Nice post guys!

    melbourne web design

    ReplyDelete