DocumentDB revisited Part 2 – Get started

I have decided to turn the two-part post into a multi-part one. So this is the second of the three entries about Azure DocumentDB. This one will take my Order example and will create a working example with it. No previous knowledge needed. The third post will handle more advanced topics.

Create your DocumentDB Account

Go into the portal (https://portal.azure.com)

Create a new Document DB from the New|Data & Storage|Azure DocumentDB section.

Create

Enter the needed information to fill in the name of your account (which also serves as a REST URL to the data so name it wisely), region, and choose a suitable resource group for it (or create a new one)

Create2

After  this you have a short wait before the service is set up completely.

Once created you can find the keys and adjust the consistency mode (but for now leave it at session level)

Create A Document DB database

Create3

Create a collection

While the previous steps did not do that much (more than logically set up our structure) we will now create our final container of the documents. This is also where we decide the scale of the operations and some indexing alternatives. Note that the container is what you pay for and having multiple containers will cost you more than having one. Remember that the containers should not be a relational table, it does NOT have to be unique for a certain Document either though you may decide with that after considerations if the load motivates it. It is however important to note that the collection are the transaction boundary for stored procedures and triggers, and the entry point to queries and CRUD operations.

Create5

Choose your Pricing Tier

Create 6

After this you should select your indexing policy. The default one is smaller and have a slightly smaller overhead with the disadvantage of not optimizing for range queries for strings. For example CustomerFirstName = “Peter” would be fine but CustomerFirstName >=”P” would work better. So depending on your task ahead you may opt for one above the other.

A .Net SDK example

Get the connection strings

Connection Strings

You will find all the needed records to access your collection in the account you created first. Select the key symbol and you can find write-access and read access keys. the url to your collection.

Create a web project in visual studio

Create any project of your liking in Visual Studio (I used a traditional web form approach but you should be able to use whatever project type (almost) that is your preference.

After creation add the Package Microsoft.Azure.DocumentDB to your project through Install-Package command or the Manage NuGet Packages  UI in the solution explorer.

NuGet

Create a model

Now we should define our model. Add a class file (in my example I have all the classes in a single file (which I normally don’t have to list the code once) my simplified class looks like below for my Order object (and related classes)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Newtonsoft.Json;

 

namespace DocumentDBSample.Models

{

public class Order

{

 [JsonProperty(PropertyName = "id")]

public string id { get; set; }

 [JsonProperty(PropertyName ="transactionDateUTC")]

public DateTime OrderDateUTC { get; set; }

 

private List<OrderLine> _Lines = new List<OrderLine>();

 [JsonProperty(PropertyName ="lines")]

public List<OrderLine> Lines {

get

{

return _Lines;

}

set

{

 _Lines = value;

}

}

 [JsonProperty(PropertyName = "deliveryAddress", NullValueHandling = NullValueHandling.Ignore)]

public Address DeliveryAddress { get; set; }

 [JsonProperty(PropertyName ="invoiceAddress", NullValueHandling = NullValueHandling.Ignore)]

public Address InvoiceAddress { get; set; }

 [JsonProperty(PropertyName = "customer", NullValueHandling = NullValueHandling.Ignore)]

public Customer Customer { get; set; }

}

public class Address

{

 [JsonProperty(PropertyName = "addressType")]

public string AddressType { get; set; }

 [JsonProperty(PropertyName = "addressLine1")]

public string AddressLine1 { get; set; }

 [JsonProperty(PropertyName = "addressLine2")]

public string AddressLine2 { get; set; }

 [JsonProperty(PropertyName = "city")]

public string City { get; set; }

 [JsonProperty(PropertyName = "zipCode")]

public int ZipCode { get; set; }

}

public class OrderLine

{

 [JsonProperty(PropertyName = "articleNo")]

public string ArticleNo { get; set; }

 [JsonProperty(PropertyName = "articleName")]

public string ArticleName { get; set; }

 [JsonProperty(PropertyName = "quantity")]

public decimal Quantity { get; set; }

 [JsonProperty(PropertyName = "itemPrice")]

public decimal ItemPrice { get; set; }

}

public class Customer

{

 [JsonProperty(PropertyName = "firstName")]

public string FirstName { get; set; }

 [JsonProperty(PropertyName = "lastName")]

public string LastName { get; set; }

 [JsonProperty(PropertyName = "email")]

public string Email { get; set; }

 [JsonProperty(PropertyName = "mobilePhone")]

public string MobilePhone { get; set; }

}

Add some application settings

To have the databases and collections a bit more flexible we add the configuration as application settings (take the values from what you created before as well as the connection information slightly above)

<appSettings>

<add key=endpoint value=https://pmsoftdocs.documents.azure.com:443//>

<add key=authKey value=“aaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbb/ccccccccccccccccccccccccc==/>

<add key=database value=OrdersDB/>

<add key=collection value=Orders/>

</appSettings>

 

Write a repository handler

Below comes a small Repository helper class that can be reused in many cases. It helps out with creating a DocumentDB client, authentication, location of the selected database and collection from the configuration parameters as well a generic document creator and generic document querier.

using Microsoft.Azure.Documents;

using Microsoft.Azure.Documents.Client;

using Microsoft.Azure.Documents.Linq;

using System;

using System.Collections.Generic;

using System.Configuration;

using System.Linq;

using System.Linq.Expressions;

using System.Text;

using System.Threading.Tasks;

namespace DocumentDBSample

{

static class DocumentDBRepository<T>

{

//Find the database

private static Database ReadDatabase()

{

var db = Client.CreateDatabaseQuery()

.Where(d => d.Id == DatabaseId)

.AsEnumerable()

.FirstOrDefault();

return db;

}

//find the collection

private static DocumentCollection ReadCollection(string databaseLink)

{

var col = Client.CreateDocumentCollectionQuery(databaseLink)

.Where(c => c.Id == CollectionId)

.AsEnumerable()

.FirstOrDefault();

return col;

}

//Expose the “database” value from configuration as a property for internal use

private static string databaseId;

private static String DatabaseId

{

get

{

if (string.IsNullOrEmpty(databaseId))

{

databaseId = ConfigurationManager.AppSettings[“database”];

}

return databaseId;

}

}

//Expose the “collection” value from configuration as a property for internal use

private static string collectionId;

private static String CollectionId

{

get

{

if (string.IsNullOrEmpty(collectionId))

{

collectionId = ConfigurationManager.AppSettings[“collection”];

}

return collectionId;

}

}

//Use the ReadDatabase function to get a reference to the database.

private static Database database;

private static Database Database

{

get

{

if (database == null)

{

database = ReadDatabase();

}

return database;

}

}

//Use the ReadCollection function to get a reference to the collection.

private static DocumentCollection collection;

private static DocumentCollection Collection

{

get

{

if (collection == null)

{

collection = ReadCollection(Database.SelfLink);

}

return collection;

}

}

//This property establishes a new connection to DocumentDB the first time it is used,

//and then reuses this instance for the duration of the application avoiding the

//overhead of instantiating a new instance of DocumentClient with each request

private static DocumentClient client;

private static DocumentClient Client

{

get

{

if (client == null)

{

string endpoint = ConfigurationManager.AppSettings[“endpoint”];

string authKey = ConfigurationManager.AppSettings[“authKey”];

Uri endpointUri = new Uri(endpoint);

client = new DocumentClient(endpointUri, authKey);

}

return client;

}

}

 

//Dynamic Query helper

public static IEnumerable<T> GetItems(Expression<Func<T, bool>> predicate)

{

return Client.CreateDocumentQuery<T>(Collection.DocumentsLink)

.Where(predicate)

.AsEnumerable();

}

//Dynamic Insert

public static async Task<Document> CreateItemAsync(T item)

{

return await Client.CreateDocumentAsync(Collection.SelfLink, item);

}

public static async Task<Document> UpdateItemAsync(T item, string itemId)

{

return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId, itemId), item);

}

 

{

return await Client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId, itemId));

}

public static async Task<Document> UpsertItemAsync(T item)

{

return await Client.UpsertDocumentAsync(Collection.SelfLink, item);

}

}

}

 

A few of them needs a short explanation…

public static async Task<Document> CreateItemAsync(T item) – this one allows you to send in a Custom Object and serielize it as JSON to Document DB. It will return a DocumentDB Document.

public static IEnumerable<T> GetItems(Expression<Func<T, bool>> predicate) – allows you to ask link queries and deserialize it to a specific object

public static async Task<Document> UpdateItemAsync(T item, string itemId) – Updates a document with a specific id.

public static async Task<Document> UpsertItemAsync(T item) allows a document to be inserted or updated without pre-fetching it

public static async Task<Document> DeleteItemAsync(string itemId) – Deletes a document with a certain id

The rest of them are pretty straightforward. We’ll see how we can use it later.

Create a document

Now we can just bind some button clicks to events. Well start by adding a submit button to create a new order which just instantiates the Order object and then send the order to the DocumentDB repository and CreateItemAsync.

protected async void btnSubmitOrder_Click(object sender, EventArgs e){

//Create order object

OrderLine ordLine = new OrderLine() { ArticleNo = “12345678”, ArticleName = “Bookcase White Rodney 233×100”, ItemPrice = 44.45M, Quantity = 1M };

Address invAddress = new Address() { AddressType = “INVOICE”, AddressLine1 = “The big City Street 2233”, AddressLine2 = “Dept Invoice”, City = “Malmö”, ZipCode = 11111 };

Customer cust = new Customer() { FirstName = “Peter”, LastName = “Mannerhult”, Email = “peter@example.com”, MobilePhone= “+4642000000” };

Order newOrder = new Order() { id = “webShop” + DateTime.UtcNow.Ticks.ToString(), OrderDateUTC = DateTime.UtcNow, Customer = cust, InvoiceAddress = invAddress};

newOrder.Lines.Add(ordLine);

//Now save itvar doc = await DocumentDBRepository<Order>.CreateItemAsync(newOrder);

System.Diagnostics.Debug.WriteLine(“OrderId: “ + doc.Id);  //Just write the id of the created document

}

 

Query documents (Linq with Lambda syntax)

With the repository class we can also easily use our existing Linq competence to query data.

var item = DocumentDBRepository<Order>.GetItems(o => o.id == “webShop635874323254478467”).FirstOrDefault();var items = DocumentDBRepository<Order>.GetItems(o => o.Customer.FirstName == “Peter”).ToList();

Using order by also works fine

var items2 = DocumentDBRepository<Order>.GetItems(o => o.Customer.FirstName == “Peter”).OrderBy(ob => ob.id).ToList();<

Query documents (SQL syntax)

If you would like to query with SQL syntax instead that will also work, we need to make an overload in the repository class to allow SQL Queries also.

public static IEnumerable<T> GetItems(string qry){

return Client.CreateDocumentQuery<T>(Collection.DocumentsLink, qry).AsEnumerable();

}

We can now use normal SQL like querying to access the records….

var orderquery4 = DocumentDBRepository<Order>.GetItems(“SELECT * FROM ORDERS WHERE ORDERS.customer.firstName = ‘Peter'”).ToList();

Update a document

Important: DocumentDB does not support partial updates to the documents. So what you are really doing it replacing the content with yours.

There are several overloads for the ReplaceDocument api method but I have added a simple one in the repository code earlier on. All you have to do is to post the document itself (your own typed class) as well as the id column.

var item = DocumentDBRepository<Order>.GetItems(o => o.id == “webShop635874323254478467”).FirstOrDefault();

item.Customer.LastName = “InTheAzureSky”;

var updatedDocument = await DocumentDBRepository<Order>.UpdateItemAsync(item, item.id);

Insert or Update

A recently added method is the Upsert method in the API. This method should be used in the scenario that you

  • have the most recent data available (you don’t need to try to load it first). This will save you the overhead of querying and then determining if you should update or insert
  • You are sure the data should be persisted and don’t want errors if the document is removed.

The Upsert is not magic but will use the id column to determine Insert or Update

var upsertedDoc = await DocumentDBRepository<Order>.UpsertItemAsync(newOrder);

Delete a Document

Delete is just as simple as the other operations. In the example below I just use the id of the record to delete. The selflink to the document would be another possible option. Note that the updatedDocument will be null.

var item = DocumentDBRepository<Order>.GetItems(o => o.id == “webShop635965004258888335”).FirstOrDefault();var updatedDocument = await DocumentDBRepository<Order>.DeleteItemAsync(item.id);

 

Summary:

This concludes the second part of this DocumentDB walk-through.

We have gone through

  1. Setup of DocumentDB in Azure
  2. Design of Documents (data design)
  3. All the CRUD operations

What is missing is some advanced topics that is needed to really get the best out of DocumentDB. So the last part of this topic will discuss stored procedures, indexing, concurrency, MongoDB API  vs DocumentDB. I hope you will join me again for that part.

Leave a comment