Microsoft.Azure.Cosmos.Samples/Usage/ItemManagement/Program.cs (613 lines of code) (raw):
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);
}
}
}