Over the past few years, the NoSQL database market has greatly matured, and NoSQL product offerings should be under consideration when designing a new solution. The relational database is still useful for most projects, but NoSQL offers some excellent options for scalability, performance, and simplicity that are difficult to address with traditional relational databases.
One of the more recent and intriguing NoSQL offerings is Cosmos DB (formerly DocumentDB), which is a cloud-hosted product that is part of Microsoft Azure. The older DocumentDB was considered a “document” style NoSQL database that mainly relied on storing JSON documents in groups called “collections.” Each document instance has a unique identifier set as its “id” property. The newer Cosmos DB also has strong support for “document” style NoSQL, but also adds support for other types of NoSQL approaches such as “column families” and “graphs.”
Here are a few great benefits of Cosmos DB, including some that currently separate it from its competitors:
- It can be setup and configured in a few minutes using only a web browser.
- It can take advantage of the large, global network of Azure datacenters to provide a globally available data source that is automatically synchronized between datacenters in near real time.
- It has excellent API support for .NET, JavaScript, and database developers. This includes the ability to write SQL queries against the NoSQL data source.
- There is support to adjust “consistency” levels, so developers have control over consistency versus performance.
Below is a quick walkthrough of setting up a new Cosmos DB and performing some traditional CRUD (Create/Read/Update/Delete) operations using .NET. This walkthrough is a “Hello World” style demo which shows a very basic setup.
Step 1: Create Test Data (and schema)
This sample includes a simple “order management” data model, which consists of a parent HardwareOrder object that contains a list of LineItems that are owned by the HardwareOrder. The HardwareOrder is also marked with Customer information and relevant dates.
This was created using two simple .NET classes that are JSON serializable with Newtonsoft. There is no other schema definition required within Cosmos DB itself. The schema is inferred from the documents that are stored.
using Newtonsoft.Json;
using System;
namespace CosmosSample.Model
{
public class HardwareOrder
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
public string CustomerNumber { get; set; }
public DateTime OrderDate { get; set; }
public DateTime ShipDate { get; set; }
public LineItem[] LineItems { get; set; }
public class override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
public LineItem
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
public string Product { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
}
Step 2: Using Azure, create a Cosmos DB instance configured for DocumentDB API, and record connection information.
Once the Cosmos DB environment has finished provisioning, review the Overview page for the new instance. Note the Region Configuration. To enable geographic replication, check off additional datacenters on the map. This provides a simple way to setup the incredibly powerful functionality of replication between datacenters.
Now go to the Keys section and record the URI and Primary Key for the database. These will be used later to connect to the Cosmos DB through the API.
Step 3: Use .NET to Read and Write Documents to Cosmos DB and even query with SQL
Create a new .NET solution and project. This should be a console application or some other type of application that will start a new process. In this case, a console application is used. Once the application is created, add a NuGet package reference for Microsoft.Azure.DocumentDB. This will provide a library that makes it easy to write code to interact with data using LINQ or SQL.
Now, add code to call into the DocumentClient class. This class is the main point of connectivity to Cosmos DB. It helps by forming service calls to the API from C# LINQ statements or SQL text.
using CosmosSample.Model;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace CosmosSample
{
public class OrderManager : IDisposable
{
private DocumentClient _cosmosConnection;
private string _databaseName;
private string _orderCollectionName;
public OrderManager()
{
// Setup client connection, and create DB and order collection if they don't exist yet
var url = "put URI here";
var key = "put key here";
_databaseName = "OrdersDB";
_orderCollectionName = "HardwareOrderCollection";
_cosmosConnection = new DocumentClient(new Uri(url), key);
// Create the database if it does not exist
_cosmosConnection.CreateDatabaseIfNotExistsAsync(new Database { Id = _databaseName });
// Create the collection for Orders in the database if it does not exist
_cosmosConnection.CreateDocumentCollectionIfNotExistsAsync(UriFactory.CreateDatabaseUri(
_databaseName), new DocumentCollection { Id = _orderCollectionName });
}
public async Task CreateOrder(HardwareOrder order)
{
await _cosmosConnection.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(
_databaseName, _orderCollectionName), order);
}
public async Task UpdateOrder(HardwareOrder order)
{
await _cosmosConnection.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(
_databaseName, _orderCollectionName, order.Id), order);
}
public async Task DeleteOrder(HardwareOrder order)
{
await _cosmosConnection.DeleteDocumentAsync(UriFactory.CreateDocumentUri(
_databaseName, _orderCollectionName, order.Id));
}
public async Task<HardwareOrder> GetOrderById(string orderId)
{
var response = await _cosmosConnection.ReadDocumentAsync<HardwareOrder>(UriFactory.CreateDocumentUri(
_databaseName, _orderCollectionName, orderId));
return response.Document;
}
public List<HardwareOrder> QueryWithLinq(string customerNumber)
{
var queryOptions = new FeedOptions { MaxItemCount = -1 };
var orders = _cosmosConnection.CreateDocumentQuery<HardwareOrder>(
UriFactory.CreateDocumentCollectionUri(
_databaseName, _orderCollectionName), queryOptions)
.Where(f => f.CustomerNumber == customerNumber);
return orders.ToList();
}
public List<HardwareOrder> QueryWithSql(string customerNumber)
{
var queryOptions = new FeedOptions { MaxItemCount = -1 };
var orders = _cosmosConnection.CreateDocumentQuery<HardwareOrder>(
UriFactory.CreateDocumentCollectionUri(_databaseName, _orderCollectionName),
$"SELECT * FROM HardwareOrder WHERE HardwareOrder.CustomerNumber = '{customerNumber}'",
queryOptions);
return orders.ToList();
}
public void Dispose()
{
if (_cosmosConnection != null)
{
_cosmosConnection.Dispose();
}
}
public List<HardwareOrder> GetTestOrders()
{
return new List<HardwareOrder>
(
new HardwareOrder[] {
new HardwareOrder
{
Id = "BIGCORP.1",
CustomerNumber = "BIGCORP123",
OrderDate = DateTime.UtcNow,
ShipDate = DateTime.UtcNow.AddDays(14),
LineItems = new LineItem[]
{
new LineItem{
Id = 555,
Product = "Laptop",
Quantity = 1500,
UnitPrice = 953.12m
},
new LineItem{
Id = 556,
Product = "Server",
Quantity = 14,
UnitPrice = 2257.16m
},
new LineItem{
Id = 557,
Product = "Power Supply",
Quantity = 223,
UnitPrice = 24.99m
},
}
},
new HardwareOrder
{
Id = "STARTUP.1",
CustomerNumber = "STARTUP123",
OrderDate = DateTime.UtcNow,
ShipDate = DateTime.UtcNow.AddDays(14),
LineItems = new LineItem[]
{
new LineItem{
Id = 1555,
Product = "Laptop",
Quantity = 15,
UnitPrice = 1250.11m
},
new LineItem{
Id = 1556,
Product = "Server",
Quantity = 2,
UnitPrice = 2699.99m
},
new LineItem{
Id = 1557,
Product = "Power Supply",
Quantity = 15,
UnitPrice = 24.99m
},
}
}
});
}
}
}
Note the two query methods – QueryWithLinq and QueryWithSql. These both allow .NET developers to use Cosmos with familiar approaches.
public List<HardwareOrder> QueryWithSql(string customerNumber)
{
var queryOptions = new FeedOptions { MaxItemCount = -1 };
var orders = _cosmosConnection.CreateDocumentQuery<HardwareOrder>(
UriFactory.CreateDocumentCollectionUri(_databaseName, _orderCollectionName),
$"SELECT * FROM HardwareOrder WHERE HardwareOrder.CustomerNumber = '{customerNumber}'",
queryOptions);
return orders.ToList();
}
Using the OrderManager class, the following code can be used in a console application to create two test HardwareOrder documents in a new collection. The code is smart enough to create the database and collection if necessary. Run the Project from Visual Studio after adding this code.
using System;
using System.Linq;
using System.Threading.Tasks;
namespace CosmosSample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("setting call to async method");
var p = new Program();
p.RunOrderSample().Wait();
Console.WriteLine("sample completed.");
Console.ReadKey();
}
private async Task RunOrderSample()
{
Console.WriteLine("Creating DB and Collection if necessary.");
// creating order CRUD class
var orderManager = new OrderManager();
Console.WriteLine("Creating Test data.");
// get some test data, there are two orders in here
var orders = orderManager.GetTestOrders();
// insert the two new orders
foreach(var order in orders)
{
await orderManager.CreateOrder(order);
}
Console.WriteLine("Updating an Order.");
// get one of the orders back,
var smallCustomerOrders = orderManager.QueryWithSql("STARTUP123");
// update the quantity on one of the line items on the order
var smallOrder = smallCustomerOrders.First();
smallOrder.LineItems.First().Quantity = 16;
// save the changes
await orderManager.UpdateOrder(smallOrder);
// look it up again to show that it saved
var smallOrderUpdated = await orderManager.GetOrderById(smallOrder.Id);
Console.WriteLine("order was updated to - {0}", smallOrderUpdated);
Console.WriteLine("the quantity of line item 1 is {0}", smallOrderUpdated.LineItems.First().Quantity);
}
}
}
Step 4: View the data in the Azure Portal
Now that two documents have been created, the Azure portal can be used to view the data, and even run some test queries using SQL.
Navigate to the Document Explorer, and two JSON files should be listed. Selecting one of the files will show its contents.
The Query Explorer allows a user to execute a sample SQL query against a given collection. Below is an example of an SQL statement that will find orders with the text “BIGCORP” in the CustomerNumber property.
Conclusion
The NoSQL tool sets are maturing, and they allow for scalability and performance in areas where traditional relational databases have struggled in the past. The latest generation of cloud-based NoSQL tools, such as Cosmos DB, allow developers to get started with a minimal amount of setup time. Aside from the power NoSQL API, Cosmos DB also supports near real-time geographic synchronization with a few mouse clicks. The support for SQL in the API can ease the learning curve for traditional relational database developers.