-
Notifications
You must be signed in to change notification settings - Fork 501
/
Copy pathProgram.cs
964 lines (836 loc) · 46 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
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);
}
}
}