public static Stream CreateStream(int gameId, string nickname) { return(new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(ParticipantLastActive.Create(gameId, nickname))))); }
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"); } }