Beispiel #1
0
        private static void RenderResult(TransactionalBatchResponse results, int resultCount)
        {
            Console.WriteLine();
            Console.WriteLine("Result View------------------------------------------");
            Console.WriteLine($"Overall Status: {results.IsSuccessStatusCode}");
            if (!results.IsSuccessStatusCode)
            {
                Console.WriteLine($"Error Message: {results.ErrorMessage}");
            }
            Console.WriteLine($"Status Code: {results.StatusCode}");
            Console.WriteLine($"Activity Id: {results.ActivityId}");


            for (int i = 0; i < resultCount; i++)
            {
                Console.WriteLine($"\tSub Task #{i + 1}------------------------------------------");
                var operationResult = results.GetOperationResultAtIndex <dynamic>(i);
                Console.WriteLine($"\t Status Code: {operationResult.StatusCode}");
                Console.WriteLine($"\t ETag: {operationResult.ETag}");
                Console.WriteLine($"\t Resource: {operationResult.Resource}");
                Console.WriteLine($"\t Status: {operationResult.IsSuccessStatusCode}");
                Console.WriteLine($"\tSub Task #{i + 1} End---------------------------------------");
                Console.WriteLine();
            }
            Console.WriteLine("Result View End---------------------------------------");
            Console.WriteLine("Press Enter to continue...");
            Console.ReadLine();
            Console.WriteLine();
        }
        public async Task BatchSingleServerResponseAsync()
        {
            List <TransactionalBatchOperationResult> expectedResults = new List <TransactionalBatchOperationResult>();
            CosmosJsonDotNetSerializer jsonSerializer = new CosmosJsonDotNetSerializer();
            TestItem testItem = new TestItem("tst");

            Stream       itemStream     = jsonSerializer.ToStream <TestItem>(testItem);
            MemoryStream resourceStream = itemStream as MemoryStream;

            if (resourceStream == null)
            {
                await itemStream.CopyToAsync(resourceStream);

                resourceStream.Position = 0;
            }

            expectedResults.Add(
                new TransactionalBatchOperationResult(HttpStatusCode.OK)
            {
                ETag           = "theETag",
                SubStatusCode  = (SubStatusCodes)1100,
                ResourceStream = resourceStream
            });
            expectedResults.Add(new TransactionalBatchOperationResult(HttpStatusCode.Conflict));

            double requestCharge = 3.6;

            TestHandler testHandler = new TestHandler(async(request, cancellationToken) =>
            {
                ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK, requestMessage: null, errorMessage: null)
                {
                    Content = await new BatchResponsePayloadWriter(expectedResults).GeneratePayloadAsync()
                };

                responseMessage.Headers.RequestCharge = requestCharge;
                return(responseMessage);
            });

            Container container = BatchUnitTests.GetContainer(testHandler);

            TransactionalBatchResponse batchResponse = await new BatchCore((ContainerInternal)container, new Cosmos.PartitionKey(BatchUnitTests.PartitionKey1))
                                                       .ReadItem("id1")
                                                       .ReadItem("id2")
                                                       .ExecuteAsync();

            Assert.AreEqual(HttpStatusCode.OK, batchResponse.StatusCode);
            Assert.AreEqual(requestCharge, batchResponse.RequestCharge);

            TransactionalBatchOperationResult <TestItem> result0 = batchResponse.GetOperationResultAtIndex <TestItem>(0);

            Assert.AreEqual(expectedResults[0].StatusCode, result0.StatusCode);
            Assert.AreEqual(expectedResults[0].SubStatusCode, result0.SubStatusCode);
            Assert.AreEqual(expectedResults[0].ETag, result0.ETag);
            Assert.AreEqual(testItem, result0.Resource);

            Assert.AreEqual(expectedResults[1].StatusCode, batchResponse[1].StatusCode);
            Assert.AreEqual(SubStatusCodes.Unknown, batchResponse[1].SubStatusCode);
            Assert.IsNull(batchResponse[1].ETag);
            Assert.IsNull(batchResponse[1].ResourceStream);
        }
        => string.IsNullOrWhiteSpace(metadata.ETag);     // As metadata has already been updated we can only use ETag to see if its a new stream.

        private static IStreamMetadata GetMetadataFromResponse(TransactionalBatchResponse response)
        {
            var result   = response.GetOperationResultAtIndex <StreamMetadata>(0);
            var metadata = result.Resource;

            metadata.ETag = result.ETag;

            return(metadata);
        }
Beispiel #4
0
        private void ValidateResponse(
            TransactionalBatchResponse response,
            int noResponseItemCount)
        {
            Assert.IsNotNull(response);
            Assert.IsTrue(response.IsSuccessStatusCode);
            Assert.IsTrue(response.RequestCharge > 0);
            Assert.IsNotNull(response.ActivityId);

            int count = 0;

            foreach (TransactionalBatchOperationResult itemResponse in response)
            {
                count++;
                if (count == noResponseItemCount)
                {
                    break;
                }

                Assert.IsTrue(itemResponse.IsSuccessStatusCode);
                Assert.IsTrue(itemResponse.StatusCode == HttpStatusCode.OK || itemResponse.StatusCode == HttpStatusCode.Created);
                Assert.IsNull(itemResponse.ResourceStream);
                Assert.IsTrue(itemResponse.RequestCharge > 0);
            }

            for (int i = 0; i < response.Count && i < noResponseItemCount; i++)
            {
                TransactionalBatchOperationResult <ToDoActivity> itemResponse = response.GetOperationResultAtIndex <ToDoActivity>(i);
                Assert.IsNull(itemResponse.Resource);
                Assert.IsNull(itemResponse.ResourceStream);
                Assert.IsTrue(response.RequestCharge > 0);
                Assert.IsNotNull(response.ActivityId);
            }

            for (int i = noResponseItemCount; i < response.Count; i++)
            {
                TransactionalBatchOperationResult <ToDoActivity> itemResponse = response.GetOperationResultAtIndex <ToDoActivity>(i);
                Assert.IsNotNull(itemResponse.Resource);
                Assert.IsNotNull(itemResponse.ResourceStream);
                Assert.IsTrue(response.RequestCharge > 0);
                Assert.IsNotNull(response.ActivityId);
            }
        }
Beispiel #5
0
    internal static IEnumerable <T> GetOperationResultResources <T>(this TransactionalBatchResponse batchResponse)
    {
        if (!batchResponse.IsSuccessStatusCode)
        {
            yield break;
        }

        for (int i = 0; i < batchResponse.Count; i++)
        {
            var operationResult = batchResponse.GetOperationResultAtIndex <T>(i);

            yield return(operationResult.IsSuccessStatusCode ? operationResult.Resource : default);
        public async Task BatchReadsOnlyAsync()
        {
            Container container = BatchTestBase.JsonContainer;

            await this.CreateJsonTestDocsAsync(container);

            TransactionalBatchResponse batchResponse = await new BatchCore((ContainerCore)container, BatchTestBase.GetPartitionKey(this.PartitionKey1))
                                                       .ReadItem(this.TestDocPk1ExistingA.Id)
                                                       .ReadItem(this.TestDocPk1ExistingB.Id)
                                                       .ReadItem(this.TestDocPk1ExistingC.Id)
                                                       .ExecuteAsync();

            BatchSinglePartitionKeyTests.VerifyBatchProcessed(batchResponse, numberOfOperations: 3);

            Assert.AreEqual(HttpStatusCode.OK, batchResponse[0].StatusCode);
            Assert.AreEqual(HttpStatusCode.OK, batchResponse[1].StatusCode);
            Assert.AreEqual(HttpStatusCode.OK, batchResponse[2].StatusCode);

            Assert.AreEqual(this.TestDocPk1ExistingA, batchResponse.GetOperationResultAtIndex <TestDoc>(0).Resource);
            Assert.AreEqual(this.TestDocPk1ExistingB, batchResponse.GetOperationResultAtIndex <TestDoc>(1).Resource);
            Assert.AreEqual(this.TestDocPk1ExistingC, batchResponse.GetOperationResultAtIndex <TestDoc>(2).Resource);
        }
Beispiel #7
0
        private async Task AddItemsToContainerAsync()
        {
            try
            {
                /* Classic Insertion
                 * foreach (var shop in CreateShopList())
                 * {
                 *  var resp = await container.CreateItemAsync<Shop>(shop);
                 *  Console.WriteLine($"Created {shop.Name} Client Time: {resp.Diagnostics.GetClientElapsedTime().TotalSeconds} \n");
                 * }
                 */

                // ACID - Limitations (Transaction only scopes a single partition!)
                // - Payload cannot exceed 2MB as per the Azure Cosmos DB request size limit
                // - Maximum execution time is 5 seconds
                // - Limit of 100 operations per TransactionalBatch to make sure the performance is as expected and within SLAs.
                using (TransactionalBatchResponse batchResponse =
                           await InsertAllDocuments(
                               container.CreateTransactionalBatch(new PartitionKey("Austria")))
                           .ExecuteAsync())
                {
                    if (!batchResponse.IsSuccessStatusCode)
                    {
                        Console.WriteLine("Batch execution failed!\n");
                        // Handle and log exception
                    }
                    else
                    {
                        // Look up interested results - eg. via typed access on operation results
                        Console.WriteLine($"Transactionbatch completed with a charge of {batchResponse.RequestCharge} RUs.");
                        for (int i = 0; i < batchResponse.Count; i++)
                        {
                            TransactionalBatchOperationResult <Shop> opResult = batchResponse.GetOperationResultAtIndex <Shop>(i);
                            Console.WriteLine($"Shop {opResult.Resource.Name} created with {opResult.ETag} ETAG.");
                        }
                    }
                }
            }
            catch (CosmosException ex) when(ex.StatusCode == HttpStatusCode.Conflict)
            {
                Console.WriteLine("Items already existing\n");
            }
        }
        private static async Task RunDemoAsync()
        {
            Container gamesContainer = Program.database.GetContainer(containerId);

            // This code demonstrates interactions by a multi-player game service that hosts games with the database to save game state.
            // In this fictional game, players move about the 10x10 map and try to find balls.
            // The objective is to collect 2 balls of the same color, or a golden ball if one appears.
            // After 5 minutes, if the game is not complete, the player with highest number of balls wins.
            int                    gameId      = 420;
            int                    playerCount = 3;
            int                    ballCount   = 4;
            List <GameBall>        balls       = new List <GameBall>();
            List <GameParticipant> players     = new List <GameParticipant>();

            Console.WriteLine("At the start of the game, the balls are added on the map, and the players are added ...");

            // The below batch request is used to create the game balls and participants in an atomic fashion.
            TransactionalBatchResponse gameStartResponse = await gamesContainer.CreateTransactionalBatch(new PartitionKey(gameId))
                                                           .CreateItem <GameBall>(GameBall.Create(gameId, Color.Red, 4, 2))
                                                           .CreateItem <GameBall>(GameBall.Create(gameId, Color.Blue, 6, 4))
                                                           .CreateItem <GameBall>(GameBall.Create(gameId, Color.Blue, 8, 7))
                                                           .CreateItem <GameBall>(GameBall.Create(gameId, Color.Red, 8, 8))
                                                           .CreateItem <GameParticipant>(GameParticipant.Create(gameId, "alice"))
                                                           .CreateItem <GameParticipant>(GameParticipant.Create(gameId, "bob"))
                                                           .CreateItem <GameParticipant>(GameParticipant.Create(gameId, "carla"))
                                                           .ExecuteAsync();

            GameParticipant alice, bob, carla;
            GameBall        firstBlueBall, secondRedBall;

            using (gameStartResponse)
            {
                // Batch requests do not throw exceptions on execution failures as long as the request is valid, so we need to check the response status explicitly.
                // A HTTP 200 (OK) StatusCode on the batch response indicates that all operations succeeded.
                // An example later demonstrates a failure case.
                if (!gameStartResponse.IsSuccessStatusCode)
                {
                    // Log and handle failure
                    LogFailure(gameStartResponse);
                    return;
                }

                // Refresh in-memory state from response.
                // The TransactionalBatchResponse has a list of TransactionalBatchOperationResult, one for each operation within the batch request in the order
                // the operations were added to the TransactionalBatch.
                for (int index = 0; index < ballCount; index++)
                {
                    // The GetOperationResultAtIndex method returns the result of the operation at the given index with a Resource deserialized to the provided type.
                    TransactionalBatchOperationResult <GameBall> gameBallResult = gameStartResponse.GetOperationResultAtIndex <GameBall>(index);
                    balls.Add(gameBallResult.Resource);
                }

                firstBlueBall = balls[1];
                secondRedBall = balls[3];

                for (int index = ballCount; index < gameStartResponse.Count; index++)
                {
                    players.Add(gameStartResponse.GetOperationResultAtIndex <GameParticipant>(index).Resource);
                }

                alice = players.Single(p => p.Nickname == "alice");
                bob   = players.Single(p => p.Nickname == "bob");
                carla = players.Single(p => p.Nickname == "carla");
            }

            PrintState(players, balls);

            Console.WriteLine("Alice goes to 6, 4 and finds a blue ball ...");
            alice.BlueCount++;

            // Upserts maybe used to replace items or create them if they are not already present.
            // An existing item maybe replaced along with concurrency checks the ETag returned in the responses of earlier requests on the item
            // or without these checks if they are not required.
            // Item deletes may also be a part of batch requests.
            TransactionalBatchResponse aliceFoundBallResponse = await gamesContainer.CreateTransactionalBatch(new PartitionKey(gameId))
                                                                .UpsertItem <ParticipantLastActive>(ParticipantLastActive.Create(gameId, "alice"))
                                                                .ReplaceItem <GameParticipant>(alice.Nickname, alice, new TransactionalBatchItemRequestOptions {
                IfMatchEtag = alice.ETag
            })
                                                                .DeleteItem(firstBlueBall.Id)
                                                                .ExecuteAsync();

            using (aliceFoundBallResponse)
            {
                if (!aliceFoundBallResponse.IsSuccessStatusCode)
                {
                    // Log and handle failure
                    alice.BlueCount--;
                    LogFailure(aliceFoundBallResponse);
                    return;
                }

                // Refresh in-memory state from response.
                balls.Remove(firstBlueBall);

                // We only update the etag as we have the rest of the state we care about here already as needed.
                alice.ETag = aliceFoundBallResponse[1].ETag;
            }

            PrintState(players, balls);

            Console.WriteLine("Bob goes to 8, 8 and finds a red ball ...");
            bob.RedCount++;

            // Stream variants for all batch operations that accept an item are also available for use when the item is available as a Stream.
            Stream bobIsActiveStream = ParticipantLastActive.CreateStream(gameId, "bob");
            Stream bobAsStream       = Program.AsStream(bob);

            using (bobIsActiveStream)
                using (bobAsStream)
                {
                    TransactionalBatchResponse bobFoundBallResponse = await gamesContainer.CreateTransactionalBatch(new PartitionKey(gameId))
                                                                      .UpsertItemStream(bobIsActiveStream)
                                                                      .ReplaceItemStream(bob.Nickname, bobAsStream, new TransactionalBatchItemRequestOptions {
                        IfMatchEtag = bob.ETag
                    })
                                                                      .DeleteItem(secondRedBall.Id)
                                                                      .ExecuteAsync();

                    using (bobFoundBallResponse)
                    {
                        if (!bobFoundBallResponse.IsSuccessStatusCode)
                        {
                            // Log and handle failure.
                            bob.RedCount--;
                            LogFailure(bobFoundBallResponse);
                            return;
                        }

                        // Refresh in-memory state from response.
                        balls.Remove(secondRedBall);

                        // The resultant item for each operation is also available as a Stream that can be used for example if the response is just
                        // going to be transferred to some other system.
                        Stream updatedPlayerAsStream = bobFoundBallResponse[1].ResourceStream;

                        bob = Program.FromStream <GameParticipant>(updatedPlayerAsStream);
                    }
                }

            PrintState(players, balls);

            Console.WriteLine("A golden ball appears near each of the players to select an instant winner ...");
            TransactionalBatchResponse goldenBallResponse = await gamesContainer.CreateTransactionalBatch(new PartitionKey(gameId))
                                                            .CreateItem <GameBall>(GameBall.Create(gameId, Color.Gold, 2, 2))
                                                            .CreateItem <GameBall>(GameBall.Create(gameId, Color.Gold, 6, 3))
                                                            // oops - there is already a ball at 8, 7
                                                            .CreateItem <GameBall>(GameBall.Create(gameId, Color.Gold, 8, 7))
                                                            .ExecuteAsync();

            using (goldenBallResponse)
            {
                // If an operation within the TransactionalBatch fails during execution, the TransactionalBatchResponse will have a status code of the failing operation.
                // The TransactionalBatchOperationResult entries within the response can be read to get details about the specific operation that failed.
                // The failing operation (for example if we have a conflict because we are trying to create an item
                // that already exists) will have the StatusCode on its corresponding TransactionalBatchOperationResult set to the actual failure status
                // (HttpStatusCode.Conflict in this example). All other result entries will have a status code of HTTP 424 Failed Dependency.
                // In case any operation within a TransactionalBatch fails, no changes from the batch will be committed.
                // Other status codes such as HTTP 429 (Too Many Requests) and HTTP 5xx on server errors may also be returned on the TransactionalBatchResponse.
                if (!goldenBallResponse.IsSuccessStatusCode)
                {
                    if (goldenBallResponse.StatusCode == HttpStatusCode.Conflict)
                    {
                        for (int index = 0; index < goldenBallResponse.Count; index++)
                        {
                            TransactionalBatchOperationResult operationResult = goldenBallResponse[index];
                            if ((int)operationResult.StatusCode == 424)
                            {
                                // This operation failed because it was in a TransactionalBatch along with another operation where the latter was the actual cause of failure.
                                continue;
                            }
                            else if (operationResult.StatusCode == HttpStatusCode.Conflict)
                            {
                                Console.WriteLine("Creation of the {0}rd golden ball failed because there was already an existing ball at that position.", index + 1);
                            }
                        }
                    }
                    else
                    {
                        // Log and handle other failures
                        LogFailure(goldenBallResponse);
                        return;
                    }
                }
            }

            PrintState(players, balls);

            Console.WriteLine("We need to end the game now; determining the winner as the player with highest balls ...");

            // Batch requests may also be used to atomically read multiple items with the same partition key.
            TransactionalBatchResponse playersResponse = await gamesContainer.CreateTransactionalBatch(new PartitionKey(gameId))
                                                         .ReadItem(alice.Nickname)
                                                         .ReadItem(bob.Nickname)
                                                         .ReadItem(carla.Nickname)
                                                         .ExecuteAsync();

            GameParticipant winner = null;
            bool            isTied = false;

            using (playersResponse)
            {
                if (!playersResponse.IsSuccessStatusCode)
                {
                    // Log and handle failure
                    LogFailure(playersResponse);
                    return;
                }

                for (int index = 0; index < playerCount; index++)
                {
                    GameParticipant current;

                    if (index == 0)
                    {
                        // The item returned can be made available as the required POCO type using GetOperationResultAtIndex.
                        // A single batch request can be used to read items that can be deserialized to different POCOs as well.
                        current = playersResponse.GetOperationResultAtIndex <GameParticipant>(index).Resource;
                    }
                    else
                    {
                        // The item returned can also instead be accessed directly as a Stream (for example to pass as-is to another component).
                        Stream aliceInfo = playersResponse[index].ResourceStream;

                        current = Program.FromStream <GameParticipant>(aliceInfo);
                    }

                    if (winner == null || current.TotalCount > winner.TotalCount)
                    {
                        winner = current;
                        isTied = false;
                    }
                    else if (current.TotalCount == winner.TotalCount)
                    {
                        isTied = true;
                    }
                }
            }

            if (!isTied)
            {
                Console.WriteLine($"{winner.Nickname} has won the game!\n");
            }
            else
            {
                Console.WriteLine("The game is a tie; there is no clear winner.\n");
            }
        }
        static async Task Main(string[] args)
        {
            IConfiguration configuration = new ConfigurationBuilder()
                                           .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                                           .Build();

            CosmosClient cosmosClient = new CosmosClientBuilder(configuration.GetConnectionString("Cosmos"))
                                        .WithApplicationName("OnDotNetRocks")
                                        .Build();

            Container container = await Program.InitializeContainerAsync(cosmosClient);

            Show    theShow;
            Episode episode1;
            Episode episode2;
            Episode episode3;

            const string showName = "OnDotNet";

            Console.WriteLine("Creating show and episodes, press ENTER to continue...");
            Console.ReadLine();
            TransactionalBatchResponse createShowResponse = await container.CreateTransactionalBatch(new PartitionKey(showName))
                                                            .CreateItem <Show>(Show.Create(showName))
                                                            .CreateItem <Episode>(Episode.Create(showName, "Cosmos DB SDK Features 1"))
                                                            .CreateItem <Episode>(Episode.Create(showName, "Cosmos DB SDK Features 2"))
                                                            .CreateItem <Episode>(Episode.Create(showName, "Cosmos DB SDK Features 3"))
                                                            .ExecuteAsync();

            using (createShowResponse)
            {
                if (!createShowResponse.IsSuccessStatusCode)
                {
                    // Log and handle failure
                    LogFailure(createShowResponse);
                    return;
                }

                TransactionalBatchOperationResult <Show> showResult = createShowResponse.GetOperationResultAtIndex <Show>(0);
                theShow = showResult.Resource;
                TransactionalBatchOperationResult <Episode> episode1Result = createShowResponse.GetOperationResultAtIndex <Episode>(1);
                episode1 = episode1Result.Resource;
                TransactionalBatchOperationResult <Episode> episode2Result = createShowResponse.GetOperationResultAtIndex <Episode>(2);
                episode2 = episode2Result.Resource;
                TransactionalBatchOperationResult <Episode> episode3Result = createShowResponse.GetOperationResultAtIndex <Episode>(3);
                episode3 = episode3Result.Resource;
            }

            Console.WriteLine($"Current Show LastUpdate {theShow.LastUpdated}");
            await Task.Delay(2000);

            theShow.LastUpdated = DateTime.UtcNow;
            Console.WriteLine($"Trying to update Show with date {theShow.LastUpdated}, press ENTER to continue...");
            Console.ReadLine();
            TransactionalBatchResponse createDuplicatedShow = await container.CreateTransactionalBatch(new PartitionKey(showName))
                                                              .ReplaceItem <Show>(theShow.Id, theShow)
                                                              .CreateItem <Episode>(Episode.Create(showName, "Cosmos DB SDK Features 2"))
                                                              .ExecuteAsync();

            using (createDuplicatedShow)
            {
                TransactionalBatchOperationResult <Show> showResult = createDuplicatedShow.GetOperationResultAtIndex <Show>(0);
                Console.WriteLine($"Batch failed > Show Replace resulted in: {showResult.StatusCode}");
                TransactionalBatchOperationResult <Episode> episodeResult = createDuplicatedShow.GetOperationResultAtIndex <Episode>(1);
                Console.WriteLine($"Batch failed > Episode Create resulted in: {episodeResult.StatusCode}");
            }

            Show storedShow = await container.ReadItemAsync <Show>(theShow.Id, new PartitionKey(showName));

            Console.WriteLine($"Stored Show date after batch fail: {storedShow.LastUpdated}");
            Console.WriteLine("Updating show and episodes, press ENTER to continue...");
            Console.ReadLine();
            theShow.LastUpdated = DateTime.UtcNow;
            episode1.AirDate    = DateTime.UtcNow.AddDays(7);
            episode2.AirDate    = DateTime.UtcNow.AddDays(14);
            TransactionalBatchResponse updateAirDate = await container.CreateTransactionalBatch(new PartitionKey(showName))
                                                       .ReplaceItem <Show>(theShow.Id, theShow)
                                                       .ReplaceItem <Episode>(episode1.Id, episode1)
                                                       .ReplaceItem <Episode>(episode2.Id, episode2)
                                                       .DeleteItem(episode3.Id)
                                                       .ExecuteAsync();

            using (updateAirDate)
            {
                if (!updateAirDate.IsSuccessStatusCode)
                {
                    // Log and handle failure
                    LogFailure(updateAirDate);
                    return;
                }
            }

            storedShow = await container.ReadItemAsync <Show>(theShow.Id, new PartitionKey(showName));

            Console.WriteLine($"Stored Show date {storedShow.LastUpdated}");
            Episode storedEpisode1 = await container.ReadItemAsync <Episode>(episode1.Id, new PartitionKey(showName));

            Console.WriteLine($"Stored Episode 1 date {storedEpisode1.AirDate}");
            Episode storedEpisode2 = await container.ReadItemAsync <Episode>(episode2.Id, new PartitionKey(showName));

            Console.WriteLine($"Stored Episode 2 date {storedEpisode2.AirDate}");
            try
            {
                await container.ReadItemAsync <Episode>(episode3.Id, new PartitionKey(showName));
            }
            catch (CosmosException ex) when(ex.StatusCode == HttpStatusCode.NotFound)
            {
                Console.WriteLine("Episode 3 was deleted.");
            }
        }