
Introduction
A change in branding for the Azure globally distributed, multi-model database service from DocumentDB to CosmosDB was admittedly, not understood by many initially. Fast forward to today, and the renaming makes sense in that the 'DocumentDB' or SQL api is only one part of the database system that comes in several flavors of apis you can use with Cosmos: SQL API (an artist formerly known as DocumentDB), MongoDB, Table, Gremlin or Cassandra. In this post we will look specifically the SQL API .NET SDK V3 change highlights.
1.) Name Changes
//Old
<PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="2.2.0" />
//
//New v3
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.4.1" />
//Old
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
//
//New v3
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Linq;
using Microsoft.Azure.Cosmos.Scripts;
using Microsoft.Azure.Cosmos.Fluent;
Package | Microsoft.Azure.DocumentDB.Core | Microsoft.Azure.Cosmos |
---|---|---|
Namespace | Removed | |
Namespace | Microsoft.Azure.Documents | Microsoft.Azure.Cosmos |
Namespace | Microsoft.Azure.Documents.Linq | Microsoft.Azure.Cosmos.Linq |
Namespace | None | Microsoft.Azure.Cosmos.Scripts |
Namespace | None | Microsoft.Azure.Cosmos.Fluent |
Class | DocumentClient | CosmosClient |
Class | ConnectionPolicy | CosmosClientOptions |
Class | DocumentCollection | Container |
Class | RequestOptions | ItemRequestOptions |
Class | FeedRequestOptions | QueryRequestOptions |
Class | SqlQuerySpec | QueryDefinition |
Class | StoredProcedure | Removed |
Class | StoredProcedureResponse<T> | StoredProcedureExecuteResponse<T> |
2.) Fluent Apis
A blog post regarding the overall api design goes in more depth. Below is an example taken from the post demonstrating the fluent api in action creating a CosmosClient and a container with indexing policies.
3.) Client Changes
The Cosmos SDK team made some great decisions here to really decompose the new CosmosClient. The old DocumentClient seemed to be used for almost all activites. Now, the respective operations hang off of the dependent objects as opposed to needing the client instance to manage documents (now items) or stored procedures. Let's look at some examples.
New Client with CosmosClientOptions
Be sure to set the CosmosClientOptions.SerializerOptions.PropertyNamingPolicy if you have a preference of camelCasing the documents server side. The Linq query processor now supports this setting when generating queries from the SDK.
//Old DocumentClient
var policy = new ConnectionPolicy()
{
ConnectionMode = ConnectionMode.Gateway,
ConnectionProtocol = Protocol.Https
};
var oldClient = new DocumentClient(new Uri("serviceUri"),
"secretAuthKey",
policy,
ConsistencyLevel.Session);
//
//New v3 CosmosClient
var options = new CosmosClientOptions()
{
ConnectionMode = ConnectionMode.Gateway,
ConsistencyLevel = ConsistencyLevel.Session,
SerializerOptions = new CosmosSerializationOptions()
};
options.SerializerOptions.PropertyNamingPolicy
= CosmosPropertyNamingPolicy.CamelCase;
// Controls the server side naming, very important for how you author queries.
// Linq queries will now respect this setting. The old SDK did not.
var newClient = new CosmosClient("serviceUri", "secretAuthKey", options);
//
4.) DocumentCollection vs Container
The SelfLink requirement also takes a dirt nap in the new v3 SDK. Thanks SDK team for putting the SelfLink parameter out of it's misery!
Let's have a moment of silence for the DocumentCollection and the optional partition key. The partition key is now required on the Container and if you didn't have a partition key defined before, it might be worth designing a partition key scheme and migrating data to a new Container.
The Container replaces the DocumentCollection and is a much more functional object with operations that used to be handled by the client. Of course, you could use the fluent api as shown above. However, below shows more of an apples to apples comparsion of the api operations to the v2 SDK.
New Container
//Old
var dbResponse = oldClient
.CreateDatabaseIfNotExistsAsync(
new Database { Id = "myDbName" });
Database db = dbResponse.Resource;
// You are on borrowed time, SelfLink...
var docCollectionResponse = oldClient
.CreateDocumentCollectionIfNotExistsAsync(
db.SelfLink, "myContainer", new RequestOptions());
DocumentCollection myContainer = docCollectionResponse.Resource;
//
//New v3
Database db = await newClient
.CreateDatabaseIfNotExistsAsync("myDbName");
// Look mom, no selfLink needed!
// But you do need that partition key.
// The partition key path here must be defined on any object in the container.
// e.g. myDocument.partitionKey
var containerResponse = await db.CreateContainerIfNotExistsAsync(
new ContainerProperties("myContainer", "/partitionKey"));
Container myContainer = containerResponse.Container;
//
5.) Document vs Item
Document operations have been renamed to Item. The Item operations now hang off the Container object instead of the Client.
Create Document/Item
//Setup
// Where DocumentClass is an object you would like to store
DocumentClass docClass = new DocumentClass();
//Old
// Notice the ref to the oldClient needed to create a document .
// And the Documents SelfLink.
Document doc = await oldClient.CreateDocumentAsync(
myContainer.DocumentsLink,
docClass,
new RequestOptions(), true);
//
//New v3
// No client object needed, just need the Container
// The new ItemRequestOptions class is optional
// Make sure to set the partition key on the object
docClass.PartitionKey = "somePartitionKey";
DocumentClass doc = await myContainer.CreateItemAsync<DocumentClass>(
docClass,
new PartitionKey(docClass.PartitionKey),
new ItemRequestOptions());
//
Query Documents/Items
Notice the class name change from SqlQuerySpec to QueryDefinition and the clean fluent api design .WithParameter(). Also, camelCasing properties did not work correctly with the old SDK when using Linq queries. That is now fixed in v3. Continuation tokens are now more accessable in the api for large resultsets via the FeedIterator<T>
Using SQL With Parameters
//Setup
// Where DocumentClass is an object you would like to query
string partitionKey = "somePartitionKey";
List<DocumentClass> results = new List<DocumentClass>();
//Old
SqlQuerySpec sqlQuery = new SqlQuerySpec("SELECT VALUE r " +
"FROM ROOT r " +
"WHERE (r.partitionKey = @partitionKey) ",
new SqlParameterCollection(){
new SqlParameter("@partitionKey", partitionKey)
});
//No return of the continuationToken here
IQueryable<dynamic> docQuery = oldClient.CreateDocumentQuery(myContainer.DocumentsLink,
sqlQuery,
new FeedOptions());
//.ToList() is needed to execute the query
foreach (dynamic doc in docQuery.ToList())
{
DocumentClass dc = doc;
results.Add(dc);
}
//
//New v3
// Notice the fluent .WithParameter goodness here
QueryDefinition sqlQuery = new QueryDefinition("SELECT VALUE r " +
"FROM ROOT r " +
"WHERE (r.partitionKey = @partitionKey) ")
.WithParameter("@partitionKey", partitionKey);
string continuationToken = null;
do
{
FeedIterator<DocumentClass> feedIterator =
myContainer.GetItemQueryIterator<DocumentClass>(
sqlQuery,
continuationToken: continuationToken);
while (feedIterator.HasMoreResults)
{
FeedResponse<DocumentClass> feedResponse = await feedIterator.ReadNextAsync();
continuationToken = feedResponse.ContinuationToken;
foreach (DocumentClass item in feedResponse)
{
results.Add(item);
}
}
} while (continuationToken != null);
//
Using Linq
//Setup
// Where DocumentClass is an object you would like to query
//Old
IOrderedQueryable<DocumentClass> docQueryable =
oldClient.CreateDocumentQuery<DocumentClass>(myContainer.DocumentsLink);
var myResults = docQueryable.ToList();
//
//New v3
IOrderedQueryable<DocumentClass> docQueryable =
myContainer.GetItemLinqQueryable<DocumentClass>(true);
var myResults = docQueryable.ToList();
//
6.) Stored Procedures
The StoredProcedure object has been traded in for a simple string stored procedure id that is referenced to execute a sproc. Also, a partition key is now required to execute the stored procedure in addition to the stored procedure id.
//Stored Proc js
function getByIdSproc_V1(id) {
var collection = getContext().getCollection();
//Query document by Id
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
'SELECT * FROM root r WHERE r.id = "' + id + '"',
function (err, feed, options) {
if (err) throw err;
if (!feed || !feed.length) {
getContext().getResponse().setBody("");
}
else {
getContext().getResponse().setBody(feed[0]);
}
});
}
//Setup
string strSprocId = "getByIdSproc_V1";
string body = *"from the js above"*
//Old
//Create the sproc
StoredProcedure getByIdSproc =
await oldClient.CreateStoredProcedureAsync(myContainer.SelfLink,
new StoredProcedure()
{
Id = strSprocId,
Body = body,
});
//Execute the sproc
string id = "someIdToLookup";
StoredProcedureResponse<DocumentClass> response =
await oldClient.ExecuteStoredProcedureAsync<DocumentClass>(getByIdSproc.SelfLink,
procedureParams: new dynamic[] { id });
DocumentClass result = response.Resource;
//
//New v3
//Execute the sproc
string id = "someIdToLookup";
string partitionKey = "somePartitionKeyToLookup";
StoredProcedureExecuteResponse<DocumentClass> response
= await myContainer.Scripts.ExecuteStoredProcedureAsync<TUser>(
strSprocId,
new PartitionKey(partitionKey),
new dynamic[] { id });
return response.Resource;
//
Summary
This article was intended to give the highlights of upgrading your project from DocumentDB .NET SDK v2 to Cosmos .NET SDK v3. First, we reviewed naming conventions and fluent api design. Also, we looked at some common examples between the old v2 code and new v3 code to expedite your upgrade. Finally, we pointed out a few breaking changes and fixes like:
- Namespace and Class mapping
- Partition Key required on creating new Containers and executing Stored Procedures
- Camel Casing properties from Linq queries are generated correctly
- Where the commonly used classes and operations live now
Open a discussion Tweet