/// <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), "Please provide a StoreItems with changes to persist."); } // 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> /// Deletes storage items from storage. /// </summary> /// <param name="keys">keys of the <see cref="IStoreItem"/> objects to remove from the store.</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="ReadAsync(string[], CancellationToken)"/> /// <seealso cref="WriteAsync(IDictionary{string, object}, CancellationToken)"/> public async Task DeleteAsync(string[] keys, CancellationToken cancellationToken) { if (keys == null || keys.Length == 0) { return; } // Ensure Initialization has been run await InitializeAsync().ConfigureAwait(false); // Parallelize deletion var tasks = keys.Select(key => _client.DeleteDocumentAsync( UriFactory.CreateDocumentUri(_databaseId, _collectionId, CosmosDBKeyEscape.EscapeKey(key)), cancellationToken: cancellationToken)); // await to deletion tasks to complete await Task.WhenAll(tasks).ConfigureAwait(false); }
public static string SanitizeKey(string key) => CosmosDBKeyEscape.EscapeKey(key);
/// <summary> /// Reads storage items from storage. /// </summary> /// <param name="keys">keys of the <see cref="IStoreItem"/> objects to read from the store.</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> /// <remarks>If the activities are successfully sent, the task result contains /// the items read, indexed by key.</remarks> /// <seealso cref="DeleteAsync(string[], CancellationToken)"/> /// <seealso cref="WriteAsync(IDictionary{string, object}, CancellationToken)"/> public async Task <IDictionary <string, object> > ReadAsync(string[] keys, CancellationToken cancellationToken) { if (keys == null || keys.Length == 0) { // No keys passed in, no result to return. return(new Dictionary <string, object>()); } // Ensure Initialization has been run await InitializeAsync().ConfigureAwait(false); var storeItems = new Dictionary <string, object>(keys.Length); var parameterSequence = string.Join(",", Enumerable.Range(0, keys.Length).Select(i => $"@id{i}")); var parameterValues = keys.Select((key, ix) => new SqlParameter($"@id{ix}", CosmosDBKeyEscape.EscapeKey(key))); var querySpec = new SqlQuerySpec { QueryText = $"SELECT c.id, c.realId, c.document, c._etag FROM c WHERE c.id in ({parameterSequence})", Parameters = new SqlParameterCollection(parameterValues), }; var query = _client.CreateDocumentQuery <DocumentStoreItem>(_collectionLink, querySpec).AsDocumentQuery(); while (query.HasMoreResults) { foreach (var doc in await query.ExecuteNextAsync <DocumentStoreItem>(cancellationToken).ConfigureAwait(false)) { var item = doc.Document.ToObject(typeof(object), _jsonSerializer); if (item is IStoreItem storeItem) { storeItem.ETag = doc.ETag; } // doc.Id cannot be used since it is escaped, read it from RealId property instead storeItems.Add(doc.ReadlId, item); } } return(storeItems); }