public async Task WriteAsync(IDictionary <string, object> changes, CancellationToken cancellationToken = default(CancellationToken)) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } if (changes.Count == 0) { return; } // Ensure Initialization has been run await InitializeAsync().ConfigureAwait(false); foreach (var change in changes) { var json = JObject.FromObject(change.Value, _jsonSerializer); // Remove etag from JSON object that was copied from IStoreItem. // The ETag information is updated as an _etag attribute in the document metadata. json.Remove("eTag"); var documentChange = new DocumentStoreItem { Id = CosmosDbKeyEscape.EscapeKey(change.Key), RealId = change.Key, Document = json, }; var etag = (change.Value as IStoreItem)?.ETag; if (etag == null || etag == "*") { // if new item or * then insert or replace unconditionally await _container.UpsertItemAsync( documentChange, new PartitionKey(documentChange.PartitionKey), cancellationToken : cancellationToken) .ConfigureAwait(false); } else if (etag.Length > 0) { // if we have an etag, do opt. concurrency replace await _container.UpsertItemAsync( documentChange, new PartitionKey(documentChange.PartitionKey), new ItemRequestOptions() { IfMatchEtag = etag, }, cancellationToken) .ConfigureAwait(false); } else { throw new Exception("etag empty"); } } }
/// <summary> /// Writes storage items to storage. /// </summary> /// <param name="changes">The items to write to storage, indexed by key.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <seealso cref="DeleteAsync(string[], CancellationToken)"/> /// <seealso cref="ReadAsync(string[], CancellationToken)"/> public async Task WriteAsync(IDictionary <string, object> changes, CancellationToken cancellationToken) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } if (changes.Count == 0) { return; } // Ensure Initialization has been run await InitializeAsync().ConfigureAwait(false); foreach (var change in changes) { var json = JObject.FromObject(change.Value, _jsonSerializer); // Remove etag from JSON object that was copied from IStoreItem. // The ETag information is updated as an _etag attribute in the document metadata. json.Remove("eTag"); var documentChange = new DocumentStoreItem { Id = CosmosDbKeyEscape.EscapeKey(change.Key), ReadlId = change.Key, Document = json, }; var etag = (change.Value as IStoreItem)?.ETag; if (etag == null || etag == "*") { // if new item or * then insert or replace unconditionaly await _client.UpsertDocumentAsync( _collectionLink, documentChange, disableAutomaticIdGeneration : true, cancellationToken : cancellationToken).ConfigureAwait(false); } else if (etag.Length > 0) { // if we have an etag, do opt. concurrency replace var uri = UriFactory.CreateDocumentUri(_databaseId, _collectionId, documentChange.Id); var ac = new AccessCondition { Condition = etag, Type = AccessConditionType.IfMatch }; await _client.ReplaceDocumentAsync( uri, documentChange, new RequestOptions { AccessCondition = ac }, cancellationToken : cancellationToken).ConfigureAwait(false); } else { throw new Exception("etag empty"); } } }
/// <summary> /// Saves store items to storage. /// </summary> /// <param name="changes">Map of items to write to storage.</param> public async Task Write(IDictionary <string, object> changes) { if (changes == null) { throw new ArgumentNullException(nameof(changes), "Please provide a StoreItems with changes to persist."); } var collectionLink = await GetCollectionLink(); foreach (var change in changes) { var json = JObject.FromObject(change.Value, _jsonSerializer); // Remove etag from JSON object that was copied from IStoreItem. // The ETag information is updated as an _etag attribute in the document metadata. json.Remove("eTag"); var documentChange = new DocumentStoreItem { Id = SanitizeKey(change.Key), ReadlId = change.Key, Document = json }; string eTag = (change.Value as IStoreItem)?.eTag; if (eTag == null || eTag == "*") { // if new item or * then insert or replace unconditionaly await _client.UpsertDocumentAsync(collectionLink, documentChange, disableAutomaticIdGeneration : true).ConfigureAwait(false); } else if (eTag.Length > 0) { // if we have an etag, do opt. concurrency replace var uri = UriFactory.CreateDocumentUri(_databaseId, _collectionId, documentChange.Id); var ac = new AccessCondition { Condition = eTag, Type = AccessConditionType.IfMatch }; await _client.ReplaceDocumentAsync(uri, documentChange, new RequestOptions { AccessCondition = ac }).ConfigureAwait(false); } else { throw new Exception("etag empty"); } } }
/// <summary> /// Inserts or updates one or more items into the Cosmos DB container. /// </summary> /// <param name="changes">A dictionary of items to be inserted or updated. The dictionary item key /// is used as the ID for the inserted / updated item.</param> /// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param> /// <returns>A Task representing the work to be executed.</returns> /// <exception cref="ArgumentNullException">Exception thrown if the changes dictionary is null.</exception> /// <exception cref="Exception">Exception thrown is the etag is empty on any of the items within the changes dictionary.</exception> public async Task WriteAsync(IDictionary <string, object> changes, CancellationToken cancellationToken = default(CancellationToken)) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } if (changes.Count == 0) { return; } // Ensure Initialization has been run await InitializeAsync().ConfigureAwait(false); foreach (var change in changes) { var json = JObject.FromObject(change.Value, _jsonSerializer); // Remove etag from JSON object that was copied from IStoreItem. // The ETag information is updated as an _etag attribute in the document metadata. json.Remove("eTag"); var documentChange = new DocumentStoreItem { Id = CosmosDbKeyEscape.EscapeKey(change.Key, _cosmosDbStorageOptions.KeySuffix, _cosmosDbStorageOptions.CompatibilityMode), RealId = change.Key, Document = json, }; var etag = (change.Value as IStoreItem)?.ETag; try { if (etag == null || etag == "*") { // if new item or * then insert or replace unconditionally await _container.UpsertItemAsync( documentChange, GetPartitionKey(documentChange.PartitionKey), cancellationToken : cancellationToken) .ConfigureAwait(false); } else if (etag.Length > 0) { // if we have an etag, do opt. concurrency replace await _container.UpsertItemAsync( documentChange, GetPartitionKey(documentChange.PartitionKey), new ItemRequestOptions() { IfMatchEtag = etag, }, cancellationToken) .ConfigureAwait(false); } else { throw new ArgumentException("etag empty"); } } catch (CosmosException ex) { // This check could potentially be performed before even attempting to upsert the item // so that a request wouldn't be made to Cosmos if it's expected to fail. // However, performing the check here ensures that this custom exception is only thrown // if Cosmos returns an error first. // This way, the nesting limit is not imposed on the Bot Framework side // and no exception will be thrown if the limit is eventually changed on the Cosmos side. if (IsNestingError(json, out var message)) { throw new InvalidOperationException(message, ex); } throw; } } }