Channels ▼
RSS

JVM Languages

Azure DocumentDB: Working with Microsoft's NoSQL Database in the Cloud


The Main method works with a using statement to create a new Microsoft.Azure.Documents.Client.DocumentClient instance with the endpoint URL and authorization key retrieved from the configuration file; then it waits for the RunAsync asynchronous method to finish its execution. The RunAsync method calls many methods that have an asynchronous execution; therefore, its declaration uses the async keyword. The Main method captures any exception that might be raised and provides detailed information in the console.

First, the RunAsync method calls the RetrieveOrCreateDatabaseAsync method that tries to retrieve a database with the ID specified in the configuration file (Entertainment). If a database with this ID doesn't exist, the method creates it. The RetrieveOrCreateDatabaseAsync method works with the DocumentClient instance (client). An easy to understand LINQ query started with client.CreateDatabaseQuery() checks whether there is a database with the specified ID:

var database = client.CreateDatabaseQuery().Where(db => db.Id == databaseId).AsEnumerable().FirstOrDefault();

The first time you execute the Console application, the client.CreateDatabaseAsync method will create the new Entertainment database with the default DocumentDB database creation options, then return a Microsoft.Azure.Documents.Database instance.

database = await client.CreateDatabaseAsync(new Database { Id = databaseId });

The Database class extends the Microsoft.Azure.Documents.Resource class and adds two properties: CollectionsLink and UsersLink. The following lines show the definition for the Database class:

namespace Microsoft.Azure.Documents
{
    public sealed class Database : Resource
    {
        public Database();

        public string CollectionsLink { get; }
        public string UsersLink { get; }
    }
}

The following code show the definition for the Resource class. Notice the use of the JsonPropertyPropertyName attributes to map JSON keys to C# properties. Each DocumentDB resource has the standard resource properties defined in the Resource class: Id , ResourceId , SelfLink , Timestamp, and ETag.

namespace Microsoft.Azure.Documents
{
    public abstract class Resource : JsonSerializable
    {
        protected Resource();

        [JsonProperty(PropertyName = "_etag")]
        public string ETag { get; internal set; }
        [JsonProperty(PropertyName = "id")]
        public virtual string Id { get; set; }
        [JsonProperty(PropertyName = "_rid")]
        public virtual string ResourceId { get; set; }
        [JsonProperty(PropertyName = "_self")]
        public string SelfLink { get; internal set; }
        [JsonProperty(PropertyName = "_ts")]
        public virtual DateTime Timestamp { get; internal set; }

        public T GetPropertyValue<T>(string propertyName);
        public void SetPropertyValue(string propertyName, object propertyValue);
    }
}

If you inspect the database instance and take a look at the value for base, you will see JSON key-value pairs. The following lines show an example of the key value pairs that define the base resource for the database. The SelfLink property is very important because it identifies the resource and you can use it as an argument for different method calls that perform actions on the DocumentDB resource — in this case, the database. SelfLink is mapped to the _self key in the JSON key value pairs.

{
  "id": "Entertainment",
  "_rid": "DvBbAA==",
  "_ts": 1409772083,
  "_self": "dbs/DvBbAA==/",
  "_etag": "00000300-0000-0000-0000-54076a330000",
  "_colls": "colls/",
  "_users": "users/"
}

After the database has been created, the RunAsync method calls the RetrieveOrCreateCollectionAsync method with database.SelfLink as one of its arguments. This method tries to retrieve a collection with the ID specified in the configuration file (Games). If a collection with this ID doesn't exist, the method creates it. The code is very similar to the previously analyzed RetrieveOrCreateDatabaseAsync method. A LINQ query started with client.CreateDocumentCollectionQuery has the SelfLink property for the desired database as an argument and checks whether there is a collection with the specified ID in the database:

var collection = client.CreateDocumentCollectionQuery(databaseSelfLink).Where(c => c.Id == id).ToArray().FirstOrDefault();

The first time you execute the Console application, the query won't retrieve any collection with the specified ID and the following call to the asynchronous client.CreateDocumentCollectionAsync method will create the new Games collection in the Entertainment database with the default DocumentDB document collection creation options and return a Microsoft.Azure.Documents.DocumentCollection instance. Notice the use of the SelfLink property for the desired database (databaseSelfLink ) to specify the database in which the document collection must be added.

collection = await client.CreateDocumentCollectionAsync(databaseSelfLink, new DocumentCollection { Id = id });

The DocumentCollection class also extends the Resource class and adds many properties. As you might guess, the SelfLink property works as a unique identifier for the document collection that you can use to make changes to this collection. Because the code used the default options, the document collection will use the automatic indexing with the consistent indexing mode.

After the document collection has been created, the RunAsync method calls the CreateGameDocumentsAsync method with collection.SelfLink as its argument. This method adds two documents to the Games document collection. The code creates documents entirely as dynamic documents without any schema by using the dynamic keyword to create dynamic objects. The use of dynamic objects is one of the possible approaches to interact with DocumentDB documents in .NET and C#. Its advantage is that I don't need to create all the classes just to insert two documents. I can modify them later to use LINQ, but that's another story. It is also possible to use any of the other additional approaches to interact with DocumentDB. Of course, each approach has its advantages and tradeoffs.

  • Use "Plain Old CLR Objects" (POCOs): You need to create the classes that will use JSON.NET serialization and deserialization.
  • Use streams to avoid the serialization overhead.
  • Use classes that extend the Microsoft.Azure.Documents.Document class to allow you to access the standard properties such as Id , ResourceId , SelfLink , Timestamp, and ETag.

The CreateGameDocumentsAsync uses the dynamic keyword to create the dynamicGame1 and dynamicGame2 objects with two different structures. Then, the following calls to the asynchronous client.CreateDocumentAsync method will add the documents to the Games collection in the Entertainment database with the default DocumentDB creation options and return a Microsoft.Azure.Documents.Document instance. Notice the use of the SelfLink property for the desired collection (collectionSelfLink) to specify the collection in which the document must be added.

The following lines show the results of inspecting document1 after the call to CreateGameDocumentsAsync with the key and value pairs of the JSON document inserted in the Games collection. Notice that Document also extends Resource, so it has the standard properties explained for Database and DocumentCollection.

Resource: {
  "gameId": "1",
  "name": "Cookie Crush in Mars",
  "releaseDate": "2014-08-10T00:00:00",
  "categories": [
    "2D",
    "puzzle",
    "addictive",
    "mobile",
    "in-game purchase"
  ],
  "played": true,
  "scores": [
    {
      "playerName": "KevinTheGreat",
      "score": 10000
    },
    {
      "playerName": "BrandonGamer",
      "score": 5800
    },
    {
      "playerName": "VanessaWonderWoman",
      "score": 10000
    }
  ],
  "id": "690f1bf2-4d53-4882-bb53-3722e5624dfc",
  "_rid": "DvBbAJb2ZAEBAAAAAAAAAA==",
  "_ts": 1409772239,
  "_self": "dbs/DvBbAA==/colls/DvBbAJb2ZAE=/docs/DvBbAJb2ZAEBAAAAAAAAAA==/",
  "_etag": "00001000-0000-0000-0000-54076acf0000",
  "_attachments": "attachments/"
}

The following lines show the results of inspecting document2 after the call to CreateGameDocumentsAsync:

Resource: {
  "gameId": "2",
  "name": "Flappy Parrot in Wonderland",
  "releaseDate": "2014-07-10T00:00:00",
  "categories": [
    "mobile",
    "completely free",
    "arcade",
    "2D"
  ],
  "played": true,
  "scores": [
    {
      "playerName": "KevinTheGreat",
      "score": 300
    }
  ],
  "levels": [
    {
      "title": "Stage 1",
      "parrots": 3,
      "rocks": 5,
      "ghosts": 1
    },
    {
      "title": "Stage 2",
      "parrots": 5,
      "rocks": 7,
      "ghosts": 2
    }
  ],
  "id": "8d52fc44-82d9-4220-8c96-9ed324ebab26",
  "_rid": "DvBbAJb2ZAECAAAAAAAAAA==",
  "_ts": 1409772365,
  "_self": "dbs/DvBbAA==/colls/DvBbAJb2ZAE=/docs/DvBbAJb2ZAECAAAAAAAAAA==/",
  "_etag": "00001100-0000-0000-0000-54076b4d0000",
  "_attachments": "attachments/"
}

Finally, the RunAsync method uses the client.CreateDocumentQuery method to execute a simple DocumentDB SQL query against the Games document collection to retrieve the document whose gameId property is equal to 1.

var game1 = client.CreateDocumentQuery(collection.SelfLink, "SELECT * FROM Games g WHERE g.gameId = \"1\"").ToArray().FirstOrDefault();

The following lines show an example of the Console output generated when the code prints a prefix of the Game whose JSON key and value pairs are going to be displayed followed by the game1 object converted to a string:

Game with Id == "1": {"gameId":"1","name":"Cookie Crush in Mars","releaseDate":"2014-08-10T00:00:00","categories":["2D","puzzle","addictive","mobile","in-game purchase"],"played":true,"scores":[{"playerName":"KevinTheGreat","score":10000},{"playerName":"BrandonGamer","score":5800},{"playerName":"VanessaWonderWoman","score":10000}],"id":"690f1bf2-4d53-4882-bb53-3722e5624dfc","_rid":"DvBbAJb2ZAEBAAAAAAAAAA==","_ts":1409772239,"_self":"dbs/DvBbAA==/colls/DvBbAJb2ZAE=/docs/DvBbAJb2ZAEBAAAAAAAAAA==/","_etag":"00001000-0000-0000-0000-54076acf0000","_attachments":"attachments/"}

The SQL is exactly the same SQL you would use with any SQL-compatible relational database management system. However, don't forget DocumentDB is a NoSQL document database service.

To use LINQ to perform a similar query and have Intellisense help while you write the query, it is necessary to declare the C# classes and specify the JsonProperty PropertyName attributes for each property whose name is different than the JSON key to which it should be mapped in the document. In this case, it is necessary to add three classes: Score, Level, and Game.

The following lines show the code for the Score class (Score.cs) with the necessary JSON keys mapped:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace DocumentDB
{
    public class Score
    {
        [JsonProperty(PropertyName = "playerName")]
        public string PlayerName { get; set; }
        [JsonProperty(PropertyName = "score")]
        public int BestScore { get; set; }
    }
}

The following lines show the code for the Level class (Level.cs) with the necessary JSON keys mapped:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace DocumentDB
{
    public class Level
    {
        [JsonProperty(PropertyName = "title")]
        public string Title { get; set; }
        [JsonProperty(PropertyName = "parrots")]
        public int ParrotsCount { get; set; }
        [JsonProperty(PropertyName = "rocks")]
        public int RocksCount { get; set; }
        [JsonProperty(PropertyName = "ghosts")]
        public int GhostsCount { get; set; }
    }
}

And here is the code for the Game class (Game.cs) with the necessary JSON keys mapped:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;

namespace DocumentDB
{
    public class Game
    {
        [JsonProperty(PropertyName = "gameId")]
        public string Id { get; set; }
        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }
        [JsonProperty(PropertyName = "releaseDate")]
        public DateTime ReleaseDate { get; set; }
        [JsonProperty(PropertyName = "categories")]
        public string[] Categories { get; set; }
        [JsonProperty(PropertyName = "played")]
        public bool IsPlayed { get; set; }
        [JsonProperty(PropertyName = "scores")]
        public Score[] Scores { get; set; }
        [JsonProperty(PropertyName = "levels")]
        public Level[] Levels { get; set; }
    }
}

After the necessary classes to support POCOs have been added to the project, it is possible to add the following lines to the RunAsync method that uses LINQ to generate a Game instance from the JSON document (retrieved form the Games document collection). Notice the call to client.CreateDocumentQuery<Game> to specify the type.

var game2 = (from g in client.CreateDocumentQuery<Game>(collection.SelfLink)
                where g.Id == "2"
                select g).FirstOrDefault();

if (game2 != null)
{
    Console.WriteLine("Game with Id == \"2\": {0}", game2);
}

Conclusion

This example should provide you a baseline that you can use to start working with DocumentDB databases, collections, documents, and queries. Of course, there are many additional things that you will need to consider when working with DocumentDB resources. In this example, I've focused on the creation of the necessary resources by using the .NET SDK and the default options, covering the database and the first query. In the next article, I'll explain other operations with documents, queries that navigate through the document hierarchy, and the use of server-side JavaScript to simplify complex queries.


Gastón Hillar is a senior contributing editor at Dr. Dobb's.

Related Article

Working with Azure DocumentDB: SQL & NoSQL Together


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.