Social Icons

twitter google plus linkedin rss feed

Pages

15.1.15

Win RT Universal app with a DocumentDB

NoSql databases have been brought to my attention a couple of thousand times in the last months and given that I am not the tidiest of the database designers and also that NoSql databases are supposed to be designed to escale I have decided to give them a go.

My platform of choice is Microsoft and looks like we have all the tools we need: Windows Apps that work the same desktops and phones and DocumentDB in Azure, fantastic, let's begin.

Let's start with the database, as it takes a while to start it will give you time to work in parallel with the visual studio in the meantime.

As I already have my azure account and everything set up I went straight to the creation of the database account: preview management portal DocumentDB Creation.

The process is explained in detail here: http://azure.microsoft.com/en-us/documentation/articles/documentdb-create-account/ 

First you need to specify a couple of parameters you won't see the page in Spanish necessarily, in fact I don't know why it is not showing it to me in English to set up the new database.



And once you are done you will be taken to a home page of the azure portal while you wait...


And while we wait we can go to the Visual Studio and start creating the projects we need. I usually say things like easy, piece of cake etc. but surely not so often today.

First of all I have created an universal app in Visual Studio 2013 as I am so outstandingly good with the user experience it will be the natural option for me to create two user interfaces, one for tablets and one for phones...


Now let's create a "Class Library (Portable for Universal Apps) to manage the DocumentDB connections and queries:


Once the DLL project was created I added a folder called Models with a class DbUser and a class Device:
namespace DocDbBridge.Models
{
    public class DbUser
    {
        public string Email { get; set; }
        public string Name { get; set; }
        public Device[] Devices { get; set; }
    }
}


namespace DocDbBridge.Models
{
    class Device
    {
        public string Name { get; set; }
        public string Brand { get; set; }
    }
}

Finally I went to the main class (which I called Connection) and added the following usings:


using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;

It doesn't work because we are missing the Microsoft Azure DocumentDB Client Library 0.9.2-preview. In order to get it I have set the DLL to use .Net Framework 4.5.1


Then got Newtonsoft.Json from Nuget:


That creates a packages.config file in the project. In it I have added a line for the Microsoft.Azure.Documents.Client and rebuilt the project. The packages.config file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Azure.Documents.Client" version="0.9.2-preview" targetFramework="portable-net451+win81+wpa81" />
  <package id="Newtonsoft.Json" version="6.0.8" targetFramework="portable-net451+win81+wpa81" />
</packages>

Finally, after the rebuild i have added a reference to the Microsoft.Azure.Documents.Client by browsing the project folders and finding the newly downloaded dll:


I have built the project again and it seems to be working, let's try to connect to the database now. Based in an example provided by Microsoft for the version 0.9.0 I have created a Connection class that goes like this:

using DocDbBridge.Models;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace DocDbBridge
{
    public class Connection : IDisposable
    {
        string endPoint = "https://chan.documents.azure.com:443/";
        string authKey = "Weird string with a lot of meaningless characters";

        DocumentClient client { get; set; }
        Database database { get; set; }

        DocumentCollection collection { get; set; }

        public Connection()
        {
             client = new DocumentClient(new Uri(endPoint), authKey);
             database = ReadOrCreateDatabase("QuickStarts");
             collection = ReadOrCreateCollection(database.SelfLink, "Documents");
             CreateDocuments(collection.SelfLink);
        }


        public DbUser QueryDocumentsLinq(string UserEmail)
        {
            // The .NET SDK for DocumentDB supports 3 different methods of Querying for Documents
            // LINQ queries, lamba and SQL


            //LINQ Lambda
            //return client.CreateDocumentQuery(collection.SelfLink).Where(u => u.Email == UserEmail).AsEnumerable().FirstOrDefault();
            return client.CreateDocumentQuery(collection.SelfLink).ToList().Where(u => u.Email == UserEmail).FirstOrDefault();
        }

        public DbUser QueryDocumentsSQL(string SqlQuery)
        {
            //3. SQL
            //var query = client.CreateDocumentQuery(collection.SelfLink, "SELECT * " +
            //                                                           "FROM UserDbs u " +
            //                                                           "WHERE u.email='andrew@stratex.com'");

            var query = client.CreateDocumentQuery(collection.SelfLink, SqlQuery);

            return query.AsEnumerable().FirstOrDefault();
        }

        public void Dispose()
        {
            Cleanup(database.SelfLink);
            client.Dispose();
        }

        #region Private Methods
        private Database ReadOrCreateDatabase(string databaseId)
        {
            // Most times you won't need to create the Database in code, someone has likely created
            // the Database already in the Azure Management Portal, but you still need a reference to the
            // Database object so that you can work with it. Therefore this first query should return a record
            // the majority of the time

            Database db = client.CreateDatabaseQuery().ToList().Where(d => d.Id == databaseId).FirstOrDefault();

                //db = client.CreateDatabaseQuery()
                //                .Where(d => d.Id == databaseId)
                //                .AsEnumerable()
                //                .FirstOrDefault();

            // In case there was no database matching, go ahead and create it. 
            if (db == null)
            {
                //Console.WriteLine("2. Database not found, creating");
                db = client.CreateDatabaseAsync(new Database { Id = databaseId }).Result;
            }

            return db;
        }

        private DocumentCollection ReadOrCreateCollection(string databaseLink, string collectionId)
        {
            DocumentCollection col = client.CreateDocumentCollectionQuery(databaseLink).ToList().Where(c => c.Id == collectionId).FirstOrDefault(); ;

                //col = client.CreateDocumentCollectionQuery(databaseLink)
                //                .Where(c => c.Id == collectionId)
                //                .AsEnumerable()
                //                .FirstOrDefault();

            // For this sample, if we found a DocumentCollection matching our criteria we are simply deleting the collection
            // and then recreating it. This is the easiest way to clear out existing documents that might be left over in a collection
            //
            // NOTE: This is not the expected behavior for a production application. 
            // You would likely do the same as with a Database previously. If found, then return, else create
            if (col != null)
            {
                //Console.WriteLine("3. Found DocumentCollection.\n3. Deleting DocumentCollection.");
                client.DeleteDocumentCollectionAsync(col.SelfLink).Wait();
            }

            //Console.WriteLine("3. Creating DocumentCollection");
            return client.CreateDocumentCollectionAsync(databaseLink, new DocumentCollection { Id = collectionId }).Result;
        }

        private void CreateDocuments(string collectionLink)
        {
            // DocumentDB provides many different ways of working with documents. 
            // 1. You can create an object that extends the Document base class
            // 2. You can use any POCO whether as it is without extending the Document base class
            // 3. You can use dynamic types
            // 4. You can even work with Streams directly.
            //
            // This sample method demonstrates only the first example
            // For more examples of other ways to work with documents please consult the samples on MSDN. 

            // Work with a well defined type that extends Document
            // In DocumetnDB every Document must have an "id" property. If you supply one, it must be unique. 
            // If you do not supply one, DocumentDB will generate a unique value for you and add it to the Document. 
            var task1 = client.CreateDocumentAsync(collectionLink, new DbUser
            {
                Email = "chan@stratex.com",
                Name = "Test user",
                Devices = new Device[]
                {
                    new Device { Name="Lumia 920", Brand="Nokia"},
                    new Device { Name="Surface 3", Brand="Microsoft"},
                }
             });

            var task2 = client.CreateDocumentAsync(collectionLink, new DbUser
            {
                Email = "andrew@stratex.com",
                Name = "Andrew",
                Devices = new Device[]
                {
                    new Device { Name="Lumia 925", Brand="Nokia"},
                    new Device { Name="Surface 3", Brand="Microsoft"},
                }
            });

            
            // Wait for the above Async operations to finish executing
            Task.WaitAll(task1, task2);
        }

        private void Cleanup(string databaseId)
        {
            client.DeleteDatabaseAsync(databaseId).Wait();
        }
        #endregion
    }
}

As you might have noticed you also need the URL and the Auth Key, you can get them from the azure portal which by now will probably have your database up and running:



After that I have added a reference to the DocDbBridge DLL in my app project:


I have, after that, executed my app, by the way, this is the code... Impressive


        private void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                using (Connection conn = new Connection())
                {
                    var user = conn.QueryDocumentsLinq("andrew@stratex.com");

                    if (user != null)
                        Result.Text = user.Name;
                    else
                        Result.Text = "User not found.";
                }
            }
            catch (Exception ex)
            {
                Result.Text = "Exception found";
            }
        }

And it has failed miserably because it needs again the Newtonsoft.Json package... I have installed it on the W8.1 app project.

After that I executed the project again, clicked my marvellous button and I got Exception: Microsoft.Azure.Documents.BadRequestException: Syntax error, unexpected end-of-file here:



client.CreateDatabaseQuery()
                                .Where(d => d.Id == databaseId)
                                .AsEnumerable()
                                .FirstOrDefault();

//And here:
client.CreateDocumentCollectionQuery(databaseLink)
                                .Where(c => c.Id == collectionId)
                                .AsEnumerable()
                                .FirstOrDefault();

It looks like there's something not working quite right in this release... I have changed the line to: 

Database db = client.CreateDatabaseQuery().ToList().Where(d => d.Id == databaseId).FirstOrDefault();

This is a huge issue because it means that you need to retrieve all of them first and then query the database... Completely Unusable! :( Could it be because I am using a piece of software that is in preview in a platform that it's not supported?... some would say yes...

Again in the Linq query the same issue, I had to change from: 

return client.CreateDocumentQuery<DbUser>(collection.SelfLink).Where(u => u.Email == UserEmail).AsEnumerable().FirstOrDefault();

To

return client.CreateDocumentQuery<DbUser>(collection.SelfLink).ToList().Where(u => u.Email == UserEmail).FirstOrDefault();

And I got again the same error when tried to execute the queries with SQL.

Wrapping up:
Upsides: It kind of works and that's really cool.
Downsides: What is a database if you cannot query? I would say... Not Ideal. 

But then again this is a preview software in an unsupported platform.... And it works more or less. Just imagine how cool would the software be in a couple of iterations!