Microsoft Azure DocumentDB is a new NoSQL document database service added to Microsoft Azure that supports JSON and JavaScript inside the database engine. DocumentDB supports SQL syntax to query JSON documents, and so enables you to leverage existing SQL knowledge while working with a NoSQL database. In this article, I provide an example of coding for this new Azure service with C#, DocumentDB SQL, and LINQ.
Working with Databases, Collections, and Documents
DocumentDB is a fully managed and schema-free JSON document database service that uses JavaScript and an HTTP REST API. It is available only as an Azure service, which provides elastic scale for both throughput and storage. There is no DocumentDB installer and you cannot work with this database on your own dedicated hardware. At the time of writing, Microsoft offers a free one month trial for new Azure accounts. In my examples below, I assume that you already have an Azure account and you are ready to work with the DocumentDB service.
DocumentDB allows you to use server-side JavaScript to add user-defined functions in your SQL queries. You can also create stored procedures and triggers in JavaScript. An SDK enables you to work with the following languages and platforms:
- .NET, including support for LINQ (Language INtegrated Query)
- Client-side JavaScript
- Node.js
- Python
If you have previous experience working with other NoSQL databases, such as MongoDB, you will be surprised with the lack of a complete administration Web console and an interactive shell. The Microsoft Azure portal allows you to create a new DocumentDB account and resource group, configure the service features, check both usage and billing statistics, access tutorials and SDKs, and get the URI and keys to access the database service. It is necessary to use either the SDK or the REST API to create and manage databases, collections, documents and users. Thus, if you want to create a database, a collection, or add some JSON documents and run a few queries, you need to select your favorite language and platform from the aforementioned list and start coding. For this article, I'll use the .NET SDK and provide examples using C#.
The .NET SDK provides classes for each DocumentDB task with many asynchronous methods, which end with the Async
suffix . Microsoft .NET Framework 4.5 introduced asynchronous methods. If you still haven't worked with the async
and await
modifiers in C#, you can read Using Asynchronous Methods in ASP.NET 4.5 and in MVC 4 to come up to speed.
Getting Started
Create a new Visual C# Windows Console application project in Visual Studio 2013. Then, run the following command within the Package Manager Console.
Install-Package Microsoft.Azure.Documents.Client -Pre
The -Pre
option indicates that you want to install a pre-release package. At the time of writing, the latest version for Microsoft.Azure.Documents.Client
was 0.9.0-preview
. You can check the latest version and the version history for this NuGet package online.
If you check the References
for the project within Solution Explorer, you will notice the installation added the Microsoft.Azure.Documents.Client
assembly and its necessary dependency, Newtonsoft.Json
. The installation also adds a new packages.config
file to the project with the following lines:
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Microsoft.Azure.Documents.Client" version="0.9.0-preview" targetFramework="net45" /> <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" /> </packages>
Before adding C# code, you need to sign in to the Azure management preview portal and create a new DocumentDB account. Just click NEW , select DocumentDB , and enter the desired name and the desired configuration based on your needs. The default 1 Capacity Unit
configuration will be enough for the sample code. After you click the Create button and Azure finishes creating the DocumentDB account, you can click on its tile and get your URI and key. Copy both the URI and Primary key values. I'll use https://example.com:443/ for the URI and DocumentDBSample
for the Primary key. However, these values won't allow you to test any valid DocumentDB service and you will need to configure your own values.
Now, go back to Visual Studio 2013 and add a reference to System.Configuration
. I don't want to store credentials in source code. So, it's necessary to use the URI, also known as endpoint URL, and the authorization key to access the DocumentDB service. Add an Application Configuration File item named appSetting.config
to the project, specify the URI value in the EndPointUrl
key, and the primary key value in the AuthorizationKey
key. The following lines show the contents for the appSetting.config
file with "https://example.com:443/"
as the EndPointUrl
value and "DocumentDBSample"
as the AuthorizationKey
value. Don't forget to replace these values with the values you copied from the Azure management portal. The DatabaseId
and CollectionId
keys define the ids for the database and the collection that the code is going to create and use.
<appSettings> <!-- Replace the value with the value you copied from the Azure management portal --> <add key="EndPointUrl" value="https://example.com:443/"/> <!-- Replace the value with the value you copied from the Azure management portal --> <add key="AuthorizationKey" value="DocumentDBSample"/> <add key="DatabaseId" value="Entertainment"/> <add key="CollectionId" value="Games"/> </appSettings>
Open the App.config
file and add the following line in the configuration
section. This way, you specifiy that the appSettings
file name is appSettings.config
.
<appSettings file="appSettings.config" />
The following lines show the code for Program.cs
, which uses the specified settings to use the Azure DocumentDB service. The code creates an Entertainment
database and a Games
collection in this database. Then, the code adds two documents to the Games
collection, and finally uses DocumentDB SQL to query the collection and retrieve one of the previously saved documents.
using System; using System.Configuration; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; using Microsoft.Azure.Documents.Linq; namespace DocumentDB { class Program { // The DocumentClient instance that allows the code to run methods that interact with the DocumentDB service private static DocumentClient client; // Retrieve the desired database id (name) from the configuration file private static readonly string databaseId = ConfigurationManager.AppSettings["DatabaseId"]; // Retrieve the desired collection id (name) from the configuration file private static readonly string collectionId = ConfigurationManager.AppSettings["CollectionId"]; // Retrieve the DocumentDB URI from the configuration file private static readonly string endpointUrl = ConfigurationManager.AppSettings["EndPointUrl"]; // Retrieve the DocumentDB Authorization Key from the configuration file private static readonly string authorizationKey = ConfigurationManager.AppSettings["AuthorizationKey"]; public static void Main(string[] args) { try { using (client = new DocumentClient(new Uri(endpointUrl), authorizationKey)) { RunAsync().Wait(); } } catch (DocumentClientException de) { Exception baseException = de.GetBaseException(); Console.WriteLine("Status code {0} error occurred: {1}, Message: {2}", de.StatusCode, de.Message, baseException.Message); } catch (Exception e) { Exception baseException = e.GetBaseException(); Console.WriteLine("Error: {0}, Message: {1}", e.Message, baseException.Message); } finally { Console.WriteLine("Please, press any key."); Console.ReadKey(); } } private static async Task<Database> RetrieveOrCreateDatabaseAsync(string id) { // Try to retrieve the database (Microsoft.Azure.Documents.Database) whose Id is equal to databaseId var database = client.CreateDatabaseQuery().Where(db => db.Id == databaseId).AsEnumerable().FirstOrDefault(); // If the previous call didn't return a Database, it is necessary to create it if (database == null) { database = await client.CreateDatabaseAsync(new Database { Id = databaseId }); Console.WriteLine("Created Database: id - {0} and selfLink - {1}", database.Id, database.SelfLink); } return database; } private static async Task<DocumentCollection> RetrieveOrCreateCollectionAsync(string databaseSelfLink, string id) { // Try to retrieve the collection (Microsoft.Azure.Documents.DocumentCollection) whose Id is equal to collectionId var collection = client.CreateDocumentCollectionQuery(databaseSelfLink).Where(c => c.Id == id).ToArray().FirstOrDefault(); // If the previous call didn't return a Collection, it is necessary to create it if (collection == null) { collection = await client.CreateDocumentCollectionAsync(databaseSelfLink, new DocumentCollection { Id = id }); } return collection; } private static async Task CreateGameDocumentsAsync(string collectionSelfLink) { // Create a dynamic object dynamic dynamicGame1 = new { gameId = "1", name = "Cookie Crush in Mars", releaseDate = new DateTime(2014, 8, 10), categories = new string[] { "2D", "puzzle", "addictive", "mobile", "in-game purchase" }, played = true, scores = new [] { new { playerName = "KevinTheGreat", score = 10000 }, new { playerName = "BrandonGamer", score = 5800 }, new { playerName = "VanessaWonderWoman", score = 10000 }, } }; var document1 = await client.CreateDocumentAsync(collectionSelfLink, dynamicGame1); // Create a dynamic object dynamic dynamicGame2 = new { gameId = "2", name = "Flappy Parrot in Wonderland", releaseDate = new DateTime(2014, 7, 10), categories = new string[] { "mobile", "completely free", "arcade", "2D" }, played = true, scores = new[] { new { playerName = "KevinTheGreat", score = 300 } }, levels = new[] { new { title = "Stage 1", parrots = 3, rocks = 5, ghosts = 1 }, new { title = "Stage 2", parrots = 5, rocks = 7, ghosts = 2 } } }; var document2 = await client.CreateDocumentAsync(collectionSelfLink, dynamicGame2); } private static async Task RunAsync() { // Try to retrieve a Database if exists, else create the Database var database = await RetrieveOrCreateDatabaseAsync(databaseId); // Try to retrieve a Document Collection, else create the Document Collection var collection = await RetrieveOrCreateCollectionAsync(database.SelfLink, collectionId); // Create two documents within the recently created or retrieved Game collection await CreateGameDocumentsAsync(collection.SelfLink); // Use DocumentDB SQL to query the documents within the Game collection var game1 = client.CreateDocumentQuery(collection.SelfLink, "SELECT * FROM Games g WHERE g.gameId = \"1\"").ToArray().FirstOrDefault(); if (game1 != null) { Console.WriteLine("Game with Id == \"1\": {0}", game1); } } } }