Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
This PR adds some sample code for JSON Patch.
The sample code is added in Item Management part.
6 contributors

Users who have contributed to this file

@j82w @ealsur @kirankumarkolli @asketagarwal @deparash @ryancrawcour
namespace Cosmos.Samples.Shared
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
// ----------------------------------------------------------------------------------------------------------
// Prerequisites -
//
// 1. An Azure Cosmos account -
// https://docs.microsoft.com/azure/cosmos-db/create-cosmosdb-resources-portal
//
// 2. Microsoft.Azure.Cosmos NuGet package -
// http://www.nuget.org/packages/Microsoft.Azure.Cosmos/
// ----------------------------------------------------------------------------------------------------------
// Sample - demonstrates the basic CRUD operations on a Item resource for Azure Cosmos
//
// 1. Basic CRUD operations on a item using regular POCOs
// 1.1 - Create a item
// 1.2 - Read a item by its Id
// 1.3 - Read all items in a container
// 1.4 - Query for items by a property other than Id
// 1.5 - Read Many items using Id and PartitionKey values.
// 1.6 - Replace a item
// 1.7 - Patch a item
// 1.8 - Upsert a item
// 1.9 - Delete a item
//
// 2. Work with dynamic objects
//
// 3. Using ETags to control execution
// 3.1 - Use ETag with ReplaceItem for optimistic concurrency
// 3.2 - Use ETag with ReadItem to only return a result if the ETag of the request does not match
//
// 4 - Access items system defined properties
//-----------------------------------------------------------------------------------------------------------
// See Also -
//
// Cosmos.Samples.Queries - We only included a VERY basic query here for completeness,
// For a detailed exploration of how to query for Items,
// including how to paginate results of queries.
//
// Cosmos.Samples.ServerSideScripts - In these examples we do simple loops to create small numbers
// of items. For insert operations where you are creating many
// items we recommend using a Stored Procedure and pass batches
// of new items to this sproc. Consult this sample for an example
// of a BulkInsert stored procedure.
// ----------------------------------------------------------------------------------------------------------
public class Program
{
private static readonly string databaseId = "samples";
private static readonly string containerId = "item-samples";
private static readonly JsonSerializer Serializer = new JsonSerializer();
//Reusable instance of ItemClient which represents the connection to a Cosmos endpoint
private static Database database = null;
private static Container container = null;
// Async main requires c# 7.1 which is set in the csproj with the LangVersion attribute
// <Main>
public static async Task Main(string[] args)
{
try
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.AddJsonFile("appSettings.json")
.Build();
string endpoint = configuration["EndPointUrl"];
if (string.IsNullOrEmpty(endpoint))
{
throw new ArgumentNullException("Please specify a valid endpoint in the appSettings.json");
}
string authKey = configuration["AuthorizationKey"];
if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret key"))
{
throw new ArgumentException("Please specify a valid AuthorizationKey in the appSettings.json");
}
//Read the Cosmos endpointUrl and authorisationKeys from configuration
//These values are available from the Azure Management Portal on the Cosmos Account Blade under "Keys"
//NB > Keep these values in a safe & secure location. Together they provide Administrative access to your Cosmos account
using (CosmosClient client = new CosmosClient(endpoint, authKey))
{
await Program.Initialize(client);
await Program.RunItemsDemo();
await Program.Cleanup();
}
}
catch (CosmosException cre)
{
Console.WriteLine(cre.ToString());
}
catch (Exception e)
{
Exception baseException = e.GetBaseException();
Console.WriteLine("Error: {0}, Message: {1}", e.Message, baseException.Message);
}
finally
{
Console.WriteLine("End of demo, press any key to exit.");
Console.ReadKey();
}
}
// </Main>
/// <summary>
/// Run basic item access methods as a console app demo
/// </summary>
/// <returns></returns>
// <RunItemsDemo>
private static async Task RunItemsDemo()
{
await Program.RunBasicOperationsOnStronglyTypedObjects();
await Program.RunBasicOperationsOnDynamicObjects();
await Program.UseETags();
await Program.UseConsistencyLevels();
await Program.AccessSystemDefinedProperties();
}
// </RunItemsDemo>
/// <summary>
/// 1. Basic CRUD operations on a item
/// 1.1 - Create a item
/// 1.2 - Read a item by its Id
/// 1.3 - Read all items in a Collection
/// 1.4 - Query for items by a property other than Id
/// 1.5 - Read Many items using Id and PartitionKey values.
/// 1.6 - Replace a item
/// 1.7 - Patch a item
/// 1.8 - Upsert a item
/// 1.9 - Delete a item
/// </summary>
// <RunBasicOperationsOnStronglyTypedObjects>
private static async Task RunBasicOperationsOnStronglyTypedObjects()
{
SalesOrder result = await Program.CreateItemsAsync();
await Program.ReadItemAsync();
await Program.ReadAllItems();
await Program.QueryItems();
await Program.ReadManyItems();
await Program.ReplaceItemAsync(result);
await Program.PatchItemAsync(result);
await Program.UpsertItemAsync();
await Program.DeleteItemAsync();
}
// </RunBasicOperationsOnStronglyTypedObjects>
// <CreateItemsAsync>
private static async Task<SalesOrder> CreateItemsAsync()
{
Console.WriteLine("\n1.1 - Creating items");
// Create a SalesOrder object. This object has nested properties and various types including numbers, DateTimes and strings.
// This can be saved as JSON as is without converting into rows/columns.
SalesOrder salesOrder = GetSalesOrderSample("SalesOrder1");
ItemResponse<SalesOrder> response = await container.CreateItemAsync(salesOrder, new PartitionKey(salesOrder.AccountNumber));
SalesOrder salesOrder1 = response;
Console.WriteLine($"\n1.1.1 - Item created {salesOrder1.Id}");
// As your app evolves, let's say your object has a new schema. You can insert SalesOrderV2 objects without any
// changes to the database tier.
SalesOrder2 salesOrder2 = GetSalesOrderV2Sample("SalesOrder2");
ItemResponse<SalesOrder2> response2 = await container.CreateItemAsync(
salesOrder2,
new PartitionKey(salesOrder2.AccountNumber),
new ItemRequestOptions()
{
// The response will have a null resource. This avoids the overhead of
// sending the item back over the network and serializing it.
EnableContentResponseOnWrite = false
});
if(response2.Resource != null)
{
throw new ArgumentException("Resource should be null");
}
Console.WriteLine($"\n1.1.2 - Item created {salesOrder2.Id}");
// For better performance create a SalesOrder object from a stream.
SalesOrder salesOrderV3 = GetSalesOrderSample("SalesOrderV3");
using (Stream stream = Program.ToStream<SalesOrder>(salesOrderV3))
{
using (ResponseMessage responseMessage = await container.CreateItemStreamAsync(stream, new PartitionKey(salesOrderV3.AccountNumber)))
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
SalesOrder streamResponse = FromStream<SalesOrder>(responseMessage.Content);
Console.WriteLine($"\n1.1.2 - Item created {streamResponse.Id}");
}
else
{
Console.WriteLine($"Create item from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
return salesOrder;
}
// </CreateItemsAsync>
// <ReadItemAsync>
private static async Task ReadItemAsync()
{
Console.WriteLine("\n1.2 - Reading Item by Id");
// Note that Reads require a partition key to be specified.
ItemResponse<SalesOrder> response = await container.ReadItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account1"),
id: "SalesOrder1");
// Log the diagnostics
Console.WriteLine($"Diagnostics for ReadItemAsync: {response.Diagnostics.ToString()}");
// You can measure the throughput consumed by any operation by inspecting the RequestCharge property
Console.WriteLine("Item read by Id {0}", response.Resource);
Console.WriteLine("Request Units Charge for reading a Item by Id {0}", response.RequestCharge);
SalesOrder readOrder = (SalesOrder)response;
// Read the same item but as a stream.
using (ResponseMessage responseMessage = await container.ReadItemStreamAsync(
partitionKey: new PartitionKey("Account1"),
id: "SalesOrder1"))
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
SalesOrder streamResponse = FromStream<SalesOrder>(responseMessage.Content);
Console.WriteLine($"\n1.2.2 - Item Read {streamResponse.Id}");
// Log the diagnostics
Console.WriteLine($"\n1.2.2 - Item Read Diagnostics: {responseMessage.Diagnostics.ToString()}");
}
else
{
Console.WriteLine($"Read item from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
// </ReadItemAsync>
// <ReadAllItems>
private static async Task ReadAllItems()
{
//******************************************************************************************************************
// 1.3 - Read all items in a container
//
// NOTE: Operations like AsEnumerable(), ToList(), ToArray() will make as many trips to the database
// as required to fetch the entire result-set. Even if you set MaxItemCount to a smaller number.
// MaxItemCount just controls how many results to fetch each trip.
//******************************************************************************************************************
Console.WriteLine("\n1.3 - Read all items with query using a specific partition key");
List<SalesOrder> allSalesForAccount1 = new List<SalesOrder>();
using (FeedIterator<SalesOrder> resultSet = container.GetItemQueryIterator<SalesOrder>(
queryDefinition: null,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey("Account1")
}))
{
while (resultSet.HasMoreResults)
{
FeedResponse<SalesOrder> response = await resultSet.ReadNextAsync();
SalesOrder sale = response.First();
Console.WriteLine($"\n1.3.1 Account Number: {sale.AccountNumber}; Id: {sale.Id};");
if (response.Diagnostics != null)
{
Console.WriteLine($" Diagnostics {response.Diagnostics.ToString()}");
}
allSalesForAccount1.AddRange(response);
}
}
Console.WriteLine($"\n1.3.2 Read all items found {allSalesForAccount1.Count} items.");
// Use the same query as before but get the cosmos response message to access the stream directly
List<SalesOrder> allSalesForAccount1FromStream = new List<SalesOrder>();
using (FeedIterator streamResultSet = container.GetItemQueryStreamIterator(
queryDefinition: null,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey("Account1")
}))
{
while (streamResultSet.HasMoreResults)
{
using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<SalesOrder> salesOrders = streamResponse.Documents.ToObject<List<SalesOrder>>();
Console.WriteLine($"\n1.3.3 - Read all items via stream {salesOrders.Count}");
allSalesForAccount1FromStream.AddRange(salesOrders);
}
else
{
Console.WriteLine($"Read all items from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
Console.WriteLine($"\n1.3.4 Read all items found {allSalesForAccount1FromStream.Count} items.");
}
if (allSalesForAccount1.Count != allSalesForAccount1FromStream.Count)
{
throw new InvalidDataException($"Both read all item operations should return the same list");
}
}
// </QueryItems>
// <QueryItems>
private static async Task QueryItems()
{
//******************************************************************************************************************
// 1.4 - Query for items by a property other than Id
//
// NOTE: Operations like AsEnumerable(), ToList(), ToArray() will make as many trips to the database
// as required to fetch the entire result-set. Even if you set MaxItemCount to a smaller number.
// MaxItemCount just controls how many results to fetch each trip.
//******************************************************************************************************************
Console.WriteLine("\n1.4 - Querying for a item using its AccountNumber property");
QueryDefinition query = new QueryDefinition(
"select * from sales s where s.AccountNumber = @AccountInput ")
.WithParameter("@AccountInput", "Account1");
List<SalesOrder> allSalesForAccount1 = new List<SalesOrder>();
using (FeedIterator<SalesOrder> resultSet = container.GetItemQueryIterator<SalesOrder>(
query,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey("Account1"),
MaxItemCount = 1
}))
{
while (resultSet.HasMoreResults)
{
FeedResponse<SalesOrder> response = await resultSet.ReadNextAsync();
SalesOrder sale = response.First();
Console.WriteLine($"\n1.4.1 Account Number: {sale.AccountNumber}; Id: {sale.Id};");
if (response.Diagnostics != null)
{
Console.WriteLine($" Diagnostics {response.Diagnostics.ToString()}");
}
allSalesForAccount1.AddRange(response);
}
}
Console.WriteLine($"\n1.4.2 Query found {allSalesForAccount1.Count} items.");
// Use the same query as before but get the cosmos response message to access the stream directly
List<SalesOrder> allSalesForAccount1FromStream = new List<SalesOrder>();
using (FeedIterator streamResultSet = container.GetItemQueryStreamIterator(
query,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey("Account1"),
MaxItemCount = 10,
MaxConcurrency = 1
}))
{
while (streamResultSet.HasMoreResults)
{
using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<SalesOrder> salesOrders = streamResponse.Documents.ToObject<List<SalesOrder>>();
Console.WriteLine($"\n1.4.3 - Item Query via stream {salesOrders.Count}");
allSalesForAccount1FromStream.AddRange(salesOrders);
}
else
{
Console.WriteLine($"Query item from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
}
Console.WriteLine($"\n1.4.4 Query found {allSalesForAccount1FromStream.Count} items.");
if (allSalesForAccount1.Count != allSalesForAccount1FromStream.Count)
{
throw new InvalidDataException($"Both query operations should return the same list");
}
}
// </QueryItems>
// <ReadManyItems>
private static async Task ReadManyItems()
{
//******************************************************************************************************************
// 1.5 - Read Many Items in a container using id and partitionkey values
//******************************************************************************************************************
Console.WriteLine("\n1.5 - ReadManyItems with Id and PartitionKey values");
// Create some items to be read back later
for (int i = 0; i < 5; i++)
{
SalesOrder salesOrder = GetSalesOrderSample($"SalesOrderForReadMany{i}");
await container.CreateItemAsync(salesOrder);
}
// Create item list with (id, pkvalue) tuples
List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>
{
("SalesOrderForReadMany1", new PartitionKey("Account1")),
("SalesOrderForReadMany2", new PartitionKey("Account1")),
("SalesOrderForReadMany4", new PartitionKey("Account1")),
};
Console.WriteLine("\nItems in read list:" + itemList.Count);
FeedResponse<SalesOrder> feedResponse = await container.ReadManyItemsAsync<SalesOrder>(
itemList);
Console.WriteLine("\nItems returned by read many api:" + feedResponse.Count);
foreach (SalesOrder salesOrder in feedResponse)
{
Console.WriteLine($"\nItem read: Id = {salesOrder.Id}, AccountNumber = {salesOrder.AccountNumber}");
}
Console.WriteLine("\nDiagnostics for ReadManyItems API: " + feedResponse.Diagnostics);
// ReadManyStreamApi
Console.WriteLine("\n1.5.2 - Using Stream API to get many items in container.");
using (ResponseMessage responseMessage = await container.ReadManyItemsStreamAsync(itemList))
{
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<SalesOrder> salesOrders = streamResponse.Documents.ToObject<List<SalesOrder>>();
Console.WriteLine($"\n Items returned from Stream API: {salesOrders.Count}");
}
else
{
Console.WriteLine($"\nReadMany items from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
Console.WriteLine("\nDiagnostics for ReadManyItemsStream API: " + responseMessage.Diagnostics);
}
}
// </ReadManyItems>
// <ReplaceItemAsync>
private static async Task ReplaceItemAsync(SalesOrder order)
{
//******************************************************************************************************************
// 1.6 - Replace a item
//
// Just update a property on an existing item and issue a Replace command
//******************************************************************************************************************
Console.WriteLine("\n1.6 - Replacing a item using its Id");
order.ShippedDate = DateTime.UtcNow;
ItemResponse<SalesOrder> response = await container.ReplaceItemAsync(
partitionKey: new PartitionKey(order.AccountNumber),
id: order.Id,
item: order);
SalesOrder updated = response.Resource;
Console.WriteLine($"Request charge of replace operation: {response.RequestCharge}");
Console.WriteLine($"Shipped date of updated item: {updated.ShippedDate}");
order.ShippedDate = DateTime.UtcNow;
using (Stream stream = Program.ToStream<SalesOrder>(order))
{
using (ResponseMessage responseMessage = await container.ReplaceItemStreamAsync(
partitionKey: new PartitionKey(order.AccountNumber),
id: order.Id,
streamPayload: stream))
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
SalesOrder streamResponse = FromStream<SalesOrder>(responseMessage.Content);
Console.WriteLine($"\n1.6.2 - Item replace via stream {streamResponse.Id}");
}
else
{
Console.WriteLine($"Replace item from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
}
// </ReplaceItemAsync>
// <PatchItemAsync>
private static async Task PatchItemAsync(SalesOrder order)
{
//******************************************************************************************************************
// 1.7 - Patch a item
//
// Just update a property of an existing item and issue a Patch command
//******************************************************************************************************************
Console.WriteLine("\n1.6 - Patching a item using its Id");
ItemResponse<SalesOrder> response = await container.PatchItemAsync<SalesOrder>(
id: order.Id,
partitionKey: new PartitionKey(order.AccountNumber),
patchOperations: new[] { PatchOperation.Replace("/TotalDue", 0) });
SalesOrder updatedSalesOrder = response.Resource;
Console.WriteLine($"TotalDue of updated item: {updatedSalesOrder.TotalDue}");
PatchItemRequestOptions patchItemRequestOptions = new PatchItemRequestOptions
{
FilterPredicate = "from c where (c.TotalDue = 0 OR NOT IS_DEFINED(c.TotalDue))"
};
response = await container.PatchItemAsync<SalesOrder>(
id: order.Id,
partitionKey: new PartitionKey(order.AccountNumber),
patchOperations: new[] { PatchOperation.Replace("/ShippedDate", DateTime.UtcNow) },
patchItemRequestOptions);
updatedSalesOrder = response.Resource;
Console.WriteLine($"\n1.6.2 - Shipped date of updated item: {updatedSalesOrder.ShippedDate}");
IReadOnlyList<PatchOperation> patchOperations = new[] { PatchOperation.Replace("/ShippedDate", DateTime.UtcNow) };
using (Stream stream = Program.ToStream<IReadOnlyList<PatchOperation>>(patchOperations))
{
using (ResponseMessage responseMessage = await container.PatchItemStreamAsync(
id: order.Id,
partitionKey: new PartitionKey(order.AccountNumber),
patchOperations: patchOperations))
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
SalesOrder streamResponse = FromStream<SalesOrder>(responseMessage.Content);
Console.WriteLine($"\n1.6.3 - Item Patch via stream {streamResponse.Id}");
}
else
{
Console.WriteLine($"Patch item from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
}
// </PatchItemAsync>
// <UpsertItemAsync>
private static async Task UpsertItemAsync()
{
Console.WriteLine("\n1.8 - Upserting a item");
SalesOrder upsertOrder = GetSalesOrderSample("SalesOrder3");
//creates the initial SalesOrder document.
//notice the response.StatusCode returned indicates a Create operation was performed
ItemResponse<SalesOrder> response = await container.UpsertItemAsync(
partitionKey: new PartitionKey(upsertOrder.AccountNumber),
item: upsertOrder);
SalesOrder upserted = response.Resource;
Console.WriteLine($"Request charge of upsert operation: {response.RequestCharge}");
Console.WriteLine($"StatusCode of this operation: { response.StatusCode}");
Console.WriteLine($"Id of upserted item: {upserted.Id}");
Console.WriteLine($"Shipped Date of upserted item: {upserted.ShippedDate}");
//update any field (other than the id, and the partitionKey, as these cannot be changed
//notice the response.StatusCode from the Upsert operation now indicates that an Update was performed
upserted.ShippedDate = DateTime.UtcNow;
response = await container.UpsertItemAsync(partitionKey: new PartitionKey(upserted.AccountNumber), item: upserted);
upserted = response.Resource;
Console.WriteLine($"Request charge of upsert operation: {response.RequestCharge}");
Console.WriteLine($"StatusCode of this operation: { response.StatusCode}");
Console.WriteLine($"Id of upserted item: {upserted.Id}");
Console.WriteLine($"ShippedDate of upserted item: {upserted.ShippedDate}");
// For better performance upsert a SalesOrder object from a stream.
SalesOrder salesOrderV4 = GetSalesOrderSample("SalesOrder4");
using (Stream stream = Program.ToStream<SalesOrder>(salesOrderV4))
{
using (ResponseMessage responseMessage = await container.UpsertItemStreamAsync(
partitionKey: new PartitionKey(salesOrderV4.AccountNumber),
streamPayload: stream))
{
// Item stream operations do not throw exceptions for better performance
if (responseMessage.IsSuccessStatusCode)
{
SalesOrder streamResponse = FromStream<SalesOrder>(responseMessage.Content);
Console.WriteLine($"\n1.7.2 - Item upserted via stream {streamResponse.Id}");
}
else
{
Console.WriteLine($"Upsert item from stream failed. Status code: {responseMessage.StatusCode} Message: {responseMessage.ErrorMessage}");
}
}
}
}
// </UpsertItemAsync>
// <DeleteItemAsync>
private static async Task DeleteItemAsync()
{
Console.WriteLine("\n1.9 - Deleting a item");
ItemResponse<SalesOrder> response = await container.DeleteItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account1"),
id: "SalesOrder3");
Console.WriteLine("Request charge of delete operation: {0}", response.RequestCharge);
Console.WriteLine("StatusCode of operation: {0}", response.StatusCode);
}
// </DeleteItemAsync>
private static T FromStream<T>(Stream stream)
{
using (stream)
{
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)stream;
}
using (StreamReader sr = new StreamReader(stream))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(sr))
{
return Program.Serializer.Deserialize<T>(jsonTextReader);
}
}
}
}
private static Stream ToStream<T>(T input)
{
MemoryStream streamPayload = new MemoryStream();
using (StreamWriter streamWriter = new StreamWriter(streamPayload, encoding: Encoding.Default, bufferSize: 1024, leaveOpen: true))
{
using (JsonWriter writer = new JsonTextWriter(streamWriter))
{
writer.Formatting = Newtonsoft.Json.Formatting.None;
Program.Serializer.Serialize(writer, input);
writer.Flush();
streamWriter.Flush();
}
}
streamPayload.Position = 0;
return streamPayload;
}
private static SalesOrder GetSalesOrderSample(string itemId)
{
SalesOrder salesOrder = new SalesOrder
{
Id = itemId,
AccountNumber = "Account1",
PurchaseOrderNumber = "PO18009186470",
OrderDate = new DateTime(2005, 7, 1),
SubTotal = 419.4589m,
TaxAmount = 12.5838m,
Freight = 472.3108m,
TotalDue = 985.018m,
Items = new SalesOrderDetail[]
{
new SalesOrderDetail
{
OrderQty = 1,
ProductId = 760,
UnitPrice = 419.4589m,
LineTotal = 419.4589m
}
},
};
// Set the "ttl" property to auto-expire sales orders in 30 days
salesOrder.TimeToLive = 60 * 60 * 24 * 30;
return salesOrder;
}
private static SalesOrder2 GetSalesOrderV2Sample(string itemId)
{
return new SalesOrder2
{
Id = itemId,
AccountNumber = "Account2",
PurchaseOrderNumber = "PO15428132599",
OrderDate = new DateTime(2005, 7, 1),
DueDate = new DateTime(2005, 7, 13),
ShippedDate = new DateTime(2005, 7, 8),
SubTotal = 6107.0820m,
TaxAmt = 586.1203m,
Freight = 183.1626m,
DiscountAmt = 1982.872m, // new property added to SalesOrder2
TotalDue = 4893.3929m,
Items = new SalesOrderDetail2[]
{
new SalesOrderDetail2
{
OrderQty = 3,
ProductCode = "A-123", // notice how in SalesOrderDetail2 we no longer reference a ProductId
ProductName = "Product 1", // instead we have decided to denormalize our schema and include
CurrencySymbol = "$", // the Product details relevant to the Order on to the Order directly
CurrencyCode = "USD", // this is a typical refactor that happens in the course of an application
UnitPrice = 17.1m, // that would have previously required schema changes and data migrations etc.
LineTotal = 5.7m
}
}
};
}
/// <summary>
/// 2. Basic CRUD operations using dynamics instead of strongly typed objects
/// Cosmos does not require objects to be typed. Applications that merge data from different data sources, or
/// need to handle evolving schemas can write data directly as JSON or dynamic objects.
/// </summary>
// <RunBasicOperationsOnDynamicObjects>
private static async Task RunBasicOperationsOnDynamicObjects()
{
Console.WriteLine("\n2. Use Dynamics");
// Create a dynamic object
dynamic salesOrder = new
{
id = "_SalesOrder5",
AccountNumber = "NewUser01",
PurchaseOrderNumber = "PO18009186470",
OrderDate = DateTime.UtcNow,
Total = 5.95,
};
Console.WriteLine("\nCreating item");
ItemResponse<dynamic> response = await container.CreateItemAsync<dynamic>(salesOrder, new PartitionKey("NewUser01"));
dynamic createdItem = response.Resource;
Console.WriteLine("Item with id {0} created", createdItem.Id);
Console.WriteLine("Request charge of operation: {0}", response.RequestCharge);
response = await container.ReadItemAsync<dynamic>(partitionKey: new PartitionKey("NewUser01"), id: "_SalesOrder5");
dynamic readItem = response.Resource;
//update a dynamic object by just creating a new Property on the fly
//Item is itself a dynamic object, so you can just use this directly too if you prefer
readItem.Add("shippedDate", DateTime.UtcNow);
//if you wish to work with a dynamic object so you don't need to use SetPropertyValue() or GetPropertyValue<T>()
//then you can cast to a dynamic
salesOrder = readItem;
salesOrder.foo = "bar";
//now do a replace using this dynamic item
//everything that is needed is contained in the readDynOrder object
//it has a .self Property
Console.WriteLine("\nReplacing item");
response = await container.ReplaceItemAsync<dynamic>(partitionKey: new PartitionKey("NewUser01"), id: "_SalesOrder5", item: salesOrder);
dynamic replaced = response.Resource;
Console.WriteLine("Request charge of operation: {0}", response.RequestCharge);
Console.WriteLine("shippedDate: {0} and foo: {1} of replaced item", replaced.shippedDate, replaced.foo);
}
// </RunBasicOperationsOnDynamicObjects>
/// <summary>
/// 3. Using ETags to control execution of operations
/// 3.1 - Use ETag to control if a ReplaceItem operation should check if ETag of request matches Item
/// 3.2 - Use ETag to control if ReadItem should only return a result if the ETag of the request does not match the Item
/// </summary>
/// <returns></returns>
// <UseETags>
private static async Task UseETags()
{
//******************************************************************************************************************
// 3.1 - Use ETag to control if a replace should succeed, or not, based on whether the ETag on the request matches
// the current ETag value of the persisted Item
//
// All items in Cosmos have an _etag field. This gets set on the server every time a item is updated.
//
// When doing a replace of a item you can opt-in to having the server only apply the Replace if the ETag
// on the request matches the ETag of the item on the server.
// If someone did an update to the same item since you read it, then the ETag on the server will not match
// and the Replace operation can be rejected.
//******************************************************************************************************************
Console.WriteLine("\n3.1 - Using optimistic concurrency when doing a ReplaceItemAsync");
//read a item
ItemResponse<SalesOrder> itemResponse = await container.ReadItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account1"),
id: "SalesOrder1");
Console.WriteLine("ETag of read item - {0}", itemResponse.ETag);
SalesOrder item = itemResponse;
//Update the total due
itemResponse.Resource.TotalDue = 1000000;
//persist the change back to the server
ItemResponse<SalesOrder> updatedDoc = await container.ReplaceItemAsync<SalesOrder>(
partitionKey: new PartitionKey(item.AccountNumber),
id: item.Id,
item: item);
Console.WriteLine("ETag of item now that is has been updated - {0}", updatedDoc.ETag);
//now, using the originally retrieved item do another update
//but set the AccessCondition class with the ETag of the originally read item and also set the AccessConditionType
//this tells the service to only do this operation if ETag on the request matches the current ETag on the item
//in our case it won't, because we updated the item and therefore gave it a new ETag
try
{
itemResponse.Resource.TotalDue = 9999999;
updatedDoc = await container.ReplaceItemAsync<SalesOrder>(itemResponse, item.Id, new PartitionKey(item.AccountNumber), new ItemRequestOptions { IfMatchEtag = itemResponse.ETag });
}
catch (CosmosException cre)
{
// now notice the failure when attempting the update
// this is because the ETag on the server no longer matches the ETag of doc (b/c it was changed in step 2)
if (cre.StatusCode == HttpStatusCode.PreconditionFailed)
{
Console.WriteLine("As expected, we have a pre-condition failure exception\n");
}
}
//*******************************************************************************************************************
// 3.2 - ETag on a ReadItemAsync request can be used to tell the server whether it should return a result, or not
//
// By setting the ETag on a ReadItemRequest along with an AccessCondition of IfNoneMatch instructs the server
// to only return a result if the ETag of the request does not match that of the persisted Item
//*******************************************************************************************************************
Console.WriteLine("\n3.2 - Using ETag to do a conditional ReadItemAsync");
// Get a item
ItemResponse<SalesOrder> response = await container.ReadItemAsync<SalesOrder>(partitionKey: new PartitionKey("Account2"), id: "SalesOrder2");
item = response;
Console.WriteLine($"Read doc with StatusCode of {response.StatusCode}");
// Get the item again with conditional access set, no item should be returned
response = await container.ReadItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account2"),
id: "SalesOrder2",
requestOptions: new ItemRequestOptions() { IfNoneMatchEtag = itemResponse.ETag });
Console.WriteLine("Read doc with StatusCode of {0}", response.StatusCode);
// Now change something on the item, then do another get and this time we should get the item back
response.Resource.TotalDue = 42;
response = await container.ReplaceItemAsync<SalesOrder>(item, item.Id, new PartitionKey(item.AccountNumber));
response = await container.ReadItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account2"),
id: "SalesOrder2",
requestOptions: new ItemRequestOptions() { IfNoneMatchEtag = itemResponse.ETag });
Console.WriteLine("Read doc with StatusCode of {0}", response.StatusCode);
}
// </UseETags>
/// <summary>
/// 4. Access items system defined properties
/// </summary>
/// <returns></returns>
// <AccessSystemDefinedProperties>
private static async Task AccessSystemDefinedProperties()
{
//******************************************************************************************************************
// Items contain attributes that are system defined:
// Timestamp : Gets the last modified timestamp associated with the item from the Azure Cosmos DB service.
// Etag : Gets the entity tag associated with the item from the Azure Cosmos DB service.
// TimeToLive : Gets the time to live in seconds of the item in the Azure Cosmos DB service.
//
// See also: https://docs.microsoft.com/azure/cosmos-db/databases-containers-items#azure-cosmos-containers
//******************************************************************************************************************
Console.WriteLine("\n4 - Accessing system defined properties");
//read a item's metadata
Metadata itemResponse = await container.ReadItemAsync<Metadata>(
partitionKey: new PartitionKey("Account1"),
id: "SalesOrder1");
Console.WriteLine("ETag of read item - {0}", itemResponse.Etag);
Console.WriteLine("TimeToLive of read item - {0}", itemResponse.TimeToLive);
Console.WriteLine("Timestamp of read item - {0}", itemResponse.Timestamp.ToShortDateString());
}
// </AccessSystemDefinedProperties>
// <UseConsistencyLevels>
private static async Task UseConsistencyLevels()
{
// Override the consistency level for a read request
ItemResponse<SalesOrder> response = await container.ReadItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account2"),
id: "SalesOrder2",
requestOptions: new ItemRequestOptions() { ConsistencyLevel = ConsistencyLevel.Eventual });
}
// </UseConsistencyLevels>
private static async Task Cleanup()
{
if (database != null)
{
await database.DeleteAsync();
}
}
private static async Task Initialize(CosmosClient client)
{
database = await client.CreateDatabaseIfNotExistsAsync(databaseId);
// Delete the existing container to prevent create item conflicts
using (await database.GetContainer(containerId).DeleteContainerStreamAsync())
{ }
// We create a partitioned collection here which needs a partition key. Partitioned collections
// can be created with very high values of provisioned throughput (up to Throughput = 250,000)
// and used to store up to 250 GB of data. You can also skip specifying a partition key to create
// single partition collections that store up to 10 GB of data.
// For this demo, we create a collection to store SalesOrders. We set the partition key to the account
// number so that we can retrieve all sales orders for an account efficiently from a single partition,
// and perform transactions across multiple sales order for a single account number.
ContainerProperties containerProperties = new ContainerProperties(containerId, partitionKeyPath: "/AccountNumber");
// Create with a throughput of 1000 RU/s
container = await database.CreateContainerIfNotExistsAsync(
containerProperties,
throughput: 1000);
}
}
}