diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/ReadFeedResponse.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/ReadFeedResponse.cs index d178c44cfe..8b70a21039 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/ReadFeedResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/ReadFeedResponse.cs @@ -8,13 +8,14 @@ namespace Microsoft.Azure.Cosmos internal class ReadFeedResponse : FeedResponse { - protected ReadFeedResponse( + internal ReadFeedResponse( HttpStatusCode httpStatusCode, - IReadOnlyCollection resources, + IEnumerable resources, + int resourceCount, Headers responseMessageHeaders, CosmosDiagnostics diagnostics) { - this.Count = resources?.Count ?? 0; + this.Count = resourceCount; this.Headers = responseMessageHeaders; this.StatusCode = httpStatusCode; this.Diagnostics = diagnostics; @@ -53,6 +54,7 @@ internal static ReadFeedResponse CreateResponse( ReadFeedResponse readFeedResponse = new ReadFeedResponse( httpStatusCode: responseMessage.StatusCode, resources: resources, + resourceCount: resources.Count, responseMessageHeaders: responseMessage.Headers, diagnostics: responseMessage.Diagnostics); diff --git a/Microsoft.Azure.Cosmos/src/ReadManyHelper.cs b/Microsoft.Azure.Cosmos/src/ReadManyHelper.cs new file mode 100644 index 0000000000..5e0d1ba727 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ReadManyHelper.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Tracing; + + internal abstract class ReadManyHelper + { + public abstract Task ExecuteReadManyRequestAsync(IReadOnlyList<(string, PartitionKey)> items, + ReadManyRequestOptions readManyRequestOptions, + ITrace trace, + CancellationToken cancellationToken); + + public abstract Task> ExecuteReadManyRequestAsync(IReadOnlyList<(string, PartitionKey)> items, + ReadManyRequestOptions readManyRequestOptions, + ITrace trace, + CancellationToken cancellationToken); + } +} diff --git a/Microsoft.Azure.Cosmos/src/ReadManyQueryHelper.cs b/Microsoft.Azure.Cosmos/src/ReadManyQueryHelper.cs new file mode 100644 index 0000000000..10520e619d --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ReadManyQueryHelper.cs @@ -0,0 +1,392 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Net; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Serializer; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Documents; + + internal sealed class ReadManyQueryHelper : ReadManyHelper + { + private readonly List partitionKeySelectors; + private readonly PartitionKeyDefinition partitionKeyDefinition; + private readonly int maxConcurrency = Environment.ProcessorCount * 10; + private readonly int maxItemsPerQuery = 1000; + private readonly ContainerCore container; + private readonly CosmosClientContext clientContext; + + public ReadManyQueryHelper(PartitionKeyDefinition partitionKeyDefinition, + ContainerCore container) + { + this.partitionKeyDefinition = partitionKeyDefinition; + this.partitionKeySelectors = this.CreatePkSelectors(partitionKeyDefinition); + this.container = container; + this.clientContext = container.ClientContext; + } + + public override async Task ExecuteReadManyRequestAsync(IReadOnlyList<(string, PartitionKey)> items, + ReadManyRequestOptions readManyRequestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + string resourceId = await this.container.GetCachedRIDAsync(cancellationToken); + IDictionary> partitionKeyRangeItemMap = + await this.CreatePartitionKeyRangeItemListMapAsync(items, cancellationToken); + + List[] queryResponses = await this.ReadManyTaskHelperAsync(partitionKeyRangeItemMap, + readManyRequestOptions, + trace, + cancellationToken); + + return this.CombineStreamsFromQueryResponses(queryResponses, resourceId, trace); // also disposes the response messages + } + + public override async Task> ExecuteReadManyRequestAsync(IReadOnlyList<(string, PartitionKey)> items, + ReadManyRequestOptions readManyRequestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + IDictionary> partitionKeyRangeItemMap = + await this.CreatePartitionKeyRangeItemListMapAsync(items, cancellationToken); + + List[] queryResponses = await this.ReadManyTaskHelperAsync(partitionKeyRangeItemMap, + readManyRequestOptions, + trace, + cancellationToken); + + return this.CombineFeedResponseFromQueryResponses(queryResponses, trace); + } + + internal async Task[]> ReadManyTaskHelperAsync(IDictionary> partitionKeyRangeItemMap, + ReadManyRequestOptions readManyRequestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + SemaphoreSlim semaphore = new SemaphoreSlim(this.maxConcurrency, this.maxConcurrency); + List>> tasks = new List>>(); + + foreach (KeyValuePair> entry in partitionKeyRangeItemMap) + { + // Fit MaxItemsPerQuery items in a single query to BE + for (int startIndex = 0; startIndex < entry.Value.Count; startIndex += this.maxItemsPerQuery) + { + // Only allow 'maxConcurrency' number of queries at a time + await semaphore.WaitAsync(); + + ITrace childTrace = trace.StartChild("Execute query for a partitionkeyrange", TraceComponent.Query, TraceLevel.Info); + int indexCopy = startIndex; + tasks.Add(Task.Run(async () => + { + try + { + QueryDefinition queryDefinition = ((this.partitionKeySelectors.Count == 1) && (this.partitionKeySelectors[0] == "[\"id\"]")) ? + this.CreateReadManyQueryDefinitionForId(entry.Value, indexCopy) : + this.CreateReadManyQueryDefinitionForOther(entry.Value, indexCopy); + + return await this.GenerateStreamResponsesForPartitionAsync(queryDefinition, + entry.Key, + readManyRequestOptions, + childTrace, + cancellationToken); + } + finally + { + semaphore.Release(); + childTrace.Dispose(); + } + })); + } + } + + return await Task.WhenAll(tasks); + } + + private async Task>> CreatePartitionKeyRangeItemListMapAsync( + IReadOnlyList<(string, PartitionKey)> items, + CancellationToken cancellationToken = default) + { + CollectionRoutingMap collectionRoutingMap = await this.container.GetRoutingMapAsync(cancellationToken); + + IDictionary> partitionKeyRangeItemMap = new + Dictionary>(); + + foreach ((string id, PartitionKey pk) item in items) + { + string effectivePartitionKeyValue = item.pk.InternalKey.GetEffectivePartitionKeyString(this.partitionKeyDefinition); + PartitionKeyRange partitionKeyRange = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyValue); + if (partitionKeyRangeItemMap.TryGetValue(partitionKeyRange, out List<(string, PartitionKey)> itemList)) + { + itemList.Add(item); + } + else + { + List<(string, PartitionKey)> newList = new List<(string, PartitionKey)> { item }; + partitionKeyRangeItemMap[partitionKeyRange] = newList; + } + } + + return partitionKeyRangeItemMap; + } + + private ResponseMessage CombineStreamsFromQueryResponses(List[] queryResponses, + string collectionRid, + ITrace trace) + { + List cosmosElements = new List(); + double requestCharge = 0; + foreach (List responseMessagesForSinglePartition in queryResponses) + { + if (responseMessagesForSinglePartition == null) + { + continue; + } + + foreach (ResponseMessage responseMessage in responseMessagesForSinglePartition) + { + using (responseMessage) + { + if (!responseMessage.IsSuccessStatusCode) + { + return new ResponseMessage(responseMessage.StatusCode) + { + Trace = trace + }; + } + + if (responseMessage is QueryResponse queryResponse) + { + cosmosElements.AddRange(queryResponse.CosmosElements); + requestCharge += queryResponse.Headers.RequestCharge; + } + else + { + throw new InvalidOperationException("Read Many is being used with Query"); + } + } + } + } + + ResponseMessage combinedResponseMessage = new ResponseMessage(System.Net.HttpStatusCode.OK) + { + Content = CosmosElementSerializer.ToStream(collectionRid, cosmosElements, ResourceType.Document), + Trace = trace + }; + combinedResponseMessage.Headers.RequestCharge = requestCharge; + return combinedResponseMessage; + } + + private FeedResponse CombineFeedResponseFromQueryResponses(List[] queryResponses, + ITrace trace) + { + int count = 0; + double requestCharge = 0; + List> typedResponses = new List>(); + foreach (List responseMessages in queryResponses) + { + if (responseMessages == null) + { + continue; + } + + foreach (ResponseMessage responseMessage in responseMessages) + { + using (responseMessage) + { + responseMessage.EnsureSuccessStatusCode(); + FeedResponse feedResponse = this.clientContext.ResponseFactory.CreateQueryFeedUserTypeResponse(responseMessage); + count += feedResponse.Count; + requestCharge += feedResponse.RequestCharge; + typedResponses.Add(feedResponse); + } + } + } + + Headers headers = new Headers + { + RequestCharge = requestCharge + }; + + ReadManyFeedResponseEnumerable enumerable = + new ReadManyFeedResponseEnumerable(typedResponses); + + return new ReadFeedResponse(System.Net.HttpStatusCode.OK, + enumerable, + count, + headers, + new CosmosTraceDiagnostics(trace)); + } + + private QueryDefinition CreateReadManyQueryDefinitionForId(List<(string, PartitionKey)> items, + int startIndex) + { + int totalItemCount = Math.Min(items.Count, startIndex + this.maxItemsPerQuery); + StringBuilder queryStringBuilder = new StringBuilder(); + queryStringBuilder.Append("SELECT * FROM c WHERE c.id IN ( "); + for (int i = startIndex; i < totalItemCount; i++) + { + queryStringBuilder.Append($"'{items[i].Item1}'"); + if (i < totalItemCount - 1) + { + queryStringBuilder.Append(","); + } + } + queryStringBuilder.Append(" )"); + + return new QueryDefinition(queryStringBuilder.ToString()); + } + + private QueryDefinition CreateReadManyQueryDefinitionForOther(List<(string, PartitionKey)> items, + int startIndex) + { + int totalItemCount = Math.Min(items.Count, startIndex + this.maxItemsPerQuery); + StringBuilder queryStringBuilder = new StringBuilder(); + SqlParameterCollection sqlParameters = new SqlParameterCollection(); + + queryStringBuilder.Append("SELECT * FROM c WHERE ( "); + for (int i = startIndex; i < totalItemCount; i++) + { + object[] pkValues = items[i].Item2.InternalKey.ToObjectArray(); + + if (pkValues.Length != this.partitionKeyDefinition.Paths.Count) + { + throw new ArgumentException("Number of components in the partition key value does not match the definition."); + } + + string pkParamName = "@param_pk" + i; + string idParamName = "@param_id" + i; + sqlParameters.Add(new SqlParameter(idParamName, items[i].Item1)); + + queryStringBuilder.Append("( "); + queryStringBuilder.Append("c.id = "); + queryStringBuilder.Append(idParamName); + for (int j = 0; j < this.partitionKeySelectors.Count; j++) + { + queryStringBuilder.Append(" AND "); + queryStringBuilder.Append("c"); + queryStringBuilder.Append(this.partitionKeySelectors[j]); + queryStringBuilder.Append(" = "); + + string pkParamNameForSinglePath = pkParamName + j; + sqlParameters.Add(new SqlParameter(pkParamNameForSinglePath, pkValues[j])); + queryStringBuilder.Append(pkParamNameForSinglePath); + } + + queryStringBuilder.Append(" )"); + + if (i < totalItemCount - 1) + { + queryStringBuilder.Append(" OR "); + } + } + queryStringBuilder.Append(" )"); + + return QueryDefinition.CreateFromQuerySpec(new SqlQuerySpec(queryStringBuilder.ToString(), + sqlParameters)); + } + + private List CreatePkSelectors(PartitionKeyDefinition partitionKeyDefinition) + { + List pathSelectors = new List(); + foreach (string path in partitionKeyDefinition.Paths) + { + IReadOnlyList pathParts = PathParser.GetPathParts(path); + List modifiedPathParts = new List(); + + foreach (string pathPart in pathParts) + { + modifiedPathParts.Add("[\"" + pathPart + "\"]"); + } + + string selector = String.Join(string.Empty, modifiedPathParts); + pathSelectors.Add(selector); + } + + return pathSelectors; + } + + private async Task> GenerateStreamResponsesForPartitionAsync(QueryDefinition queryDefinition, + PartitionKeyRange partitionKeyRange, + ReadManyRequestOptions readManyRequestOptions, + ITrace trace, + CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return null; + } + + List pages = new List(); + FeedIteratorInternal feedIterator = (FeedIteratorInternal)this.container.GetItemQueryStreamIterator( + new FeedRangeEpk(partitionKeyRange.ToRange()), + queryDefinition, + continuationToken: null, + requestOptions: readManyRequestOptions?.ConvertToQueryRequestOptions()); + while (feedIterator.HasMoreResults) + { + try + { + ResponseMessage responseMessage = await feedIterator.ReadNextAsync(trace, cancellationToken); + if (!responseMessage.IsSuccessStatusCode) + { + this.CancelCancellationToken(cancellationToken); + } + pages.Add(responseMessage); + } + catch + { + this.CancelCancellationToken(cancellationToken); + throw; + } + } + + return pages; + } + + private void CancelCancellationToken(CancellationToken cancellationToken) + { + using (CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + cancellationTokenSource.Cancel(); + } + } + + private class ReadManyFeedResponseEnumerable : IEnumerable + { + private readonly List> typedResponses; + + public ReadManyFeedResponseEnumerable(List> queryResponses) + { + this.typedResponses = queryResponses; + } + + public IEnumerator GetEnumerator() + { + foreach (FeedResponse feedResponse in this.typedResponses) + { + foreach (T item in feedResponse) + { + yield return item; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/ReadManyRequestOptions.cs b/Microsoft.Azure.Cosmos/src/ReadManyRequestOptions.cs new file mode 100644 index 0000000000..6bb6fd4c04 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/ReadManyRequestOptions.cs @@ -0,0 +1,74 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + /// + /// The Cosmos query request options + /// + public class ReadManyRequestOptions : RequestOptions + { + /// + /// Gets or sets the consistency level required for the request in the Azure Cosmos DB service. + /// + /// + /// The consistency level required for the request. + /// + /// + /// Azure Cosmos DB offers 5 different consistency levels. Strong, Bounded Staleness, Session, Consistent Prefix and Eventual - in order of strongest to weakest consistency. + /// + /// While this is set at a database account level, Azure Cosmos DB allows a developer to override the default consistency level + /// for each individual request. + /// + /// + public ConsistencyLevel? ConsistencyLevel + { + get => this.BaseConsistencyLevel; + set => this.BaseConsistencyLevel = value; + } + + /// + /// Gets or sets the token for use with session consistency in the Azure Cosmos DB service. + /// + /// + /// The token for use with session consistency. + /// + /// + /// + /// One of the for Azure Cosmos DB is Session. In fact, this is the default level applied to accounts. + /// + /// When working with Session consistency, each new write request to Azure Cosmos DB is assigned a new SessionToken. + /// The CosmosClient will use this token internally with each read/query request to ensure that the set consistency level is maintained. + /// + /// + /// In some scenarios you need to manage this Session yourself; + /// Consider a web application with multiple nodes, each node will have its own instance of + /// If you wanted these nodes to participate in the same session (to be able read your own writes consistently across web tiers) + /// you would have to send the SessionToken from of the write action on one node + /// to the client tier, using a cookie or some other mechanism, and have that token flow back to the web tier for subsequent reads. + /// If you are using a round-robin load balancer which does not maintain session affinity between requests, such as the Azure Load Balancer, + /// the read could potentially land on a different node to the write request, where the session was created. + /// + /// + /// + /// If you do not flow the Azure Cosmos DB SessionToken across as described above you could end up with inconsistent read results for a period of time. + /// + /// + /// + /// + public string SessionToken { get; set; } + + internal QueryRequestOptions ConvertToQueryRequestOptions() + { + return new QueryRequestOptions + { + ConsistencyLevel = this.ConsistencyLevel, + SessionToken = this.SessionToken, + IfMatchEtag = this.IfMatchEtag, + IfNoneMatchEtag = this.IfNoneMatchEtag, + Properties = this.Properties + }; + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs index 7aacdd0615..2cc7af516d 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs @@ -122,13 +122,13 @@ public ConsistencyLevel? ConsistencyLevel /// One of the for Azure Cosmos DB is Session. In fact, this is the default level applied to accounts. /// /// When working with Session consistency, each new write request to Azure Cosmos DB is assigned a new SessionToken. - /// The DocumentClient will use this token internally with each read/query request to ensure that the set consistency level is maintained. + /// The CosmosClient will use this token internally with each read/query request to ensure that the set consistency level is maintained. /// /// /// In some scenarios you need to manage this Session yourself; - /// Consider a web application with multiple nodes, each node will have its own instance of + /// Consider a web application with multiple nodes, each node will have its own instance of /// If you wanted these nodes to participate in the same session (to be able read your own writes consistently across web tiers) - /// you would have to send the SessionToken from of the write action on one node + /// you would have to send the SessionToken from of the write action on one node /// to the client tier, using a cookie or some other mechanism, and have that token flow back to the web tier for subsequent reads. /// If you are using a round-robin load balancer which does not maintain session affinity between requests, such as the Azure Load Balancer, /// the read could potentially land on a different node to the write request, where the session was created. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 4df29fb5f8..b65582d104 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -620,6 +620,77 @@ public abstract Task> ReplaceItemAsync( ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default); + /// + /// Reads multiple items from a container using Id and PartitionKey values. + /// + /// List of item.Id and + /// Request Options for ReadMany Operation + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps a containing the response. + /// + /// + /// + /// itemList = new List<(string, PartitionKey)> + /// { + /// ("Id1", new PartitionKey("pkValue1")), + /// ("Id2", new PartitionKey("pkValue2")), + /// ("Id3", new PartitionKey("pkValue3")) + /// }; + /// + /// using (ResponseMessage responseMessage = await this.Container.ReadManyItemsStreamAsync(itemList)) + /// { + /// using (Stream stream = response.ReadBodyAsync()) + /// { + /// //Read or do other operations with the stream + /// using (StreamReader streamReader = new StreamReader(stream)) + /// { + /// string content = streamReader.ReadToEndAsync(); + /// } + /// } + /// } + /// ]]> + /// + /// + public abstract Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default); + + /// + /// Reads multiple items from a container using Id and PartitionKey values. + /// + /// List of item.Id and + /// Request Options for ReadMany Operation + /// (Optional) representing request cancellation. + /// + /// A containing a which wraps the typed items. + /// + /// + /// + /// itemList = new List<(string, PartitionKey)> + /// { + /// ("Id1", new PartitionKey("pkValue1")), + /// ("Id2", new PartitionKey("pkValue2")), + /// ("Id3", new PartitionKey("pkValue3")) + /// }; + /// + /// FeedResponse feedResponse = this.Container.ReadManyItemsAsync(itemList); + /// ]]> + /// + /// + public abstract Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default); + #if PREVIEW /// /// Patches an item in the Azure Cosmos service as an asynchronous operation. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 3ce5414337..ce44a5bedc 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -16,6 +16,8 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.ChangeFeed.FeedProcessing; using Microsoft.Azure.Cosmos.ChangeFeed.Pagination; + using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.Common; using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.Json; using Microsoft.Azure.Cosmos.Linq; @@ -27,9 +29,11 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; /// /// Used to perform operations on items. There are two different types of operations. @@ -278,6 +282,56 @@ public override FeedIterator GetItemQueryStreamIterator( requestOptions: requestOptions); } + public async Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ITrace trace, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + ReadManyHelper readManyHelper = new ReadManyQueryHelper(await this.GetPartitionKeyDefinitionAsync(), + this); + + return await readManyHelper.ExecuteReadManyRequestAsync(items, + readManyRequestOptions, + trace, + cancellationToken); + } + + public async Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ITrace trace, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + ReadManyHelper readManyHelper = new ReadManyQueryHelper(await this.GetPartitionKeyDefinitionAsync(), + this); + + return await readManyHelper.ExecuteReadManyRequestAsync(items, + readManyRequestOptions, + trace, + cancellationToken); + } + /// /// Used in the compute gateway to support legacy gateway interface. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 6905a5a680..a3a11fb18e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -307,6 +307,28 @@ public override Task> PatchItemAsync( (trace) => base.PatchItemAsync(id, partitionKey, patchOperations, trace, requestOptions, cancellationToken)); } + public override Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + nameof(ReadManyItemsStreamAsync), + null, + (trace) => base.ReadManyItemsStreamAsync(items, trace, readManyRequestOptions, cancellationToken)); + } + + public override Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + return this.ClientContext.OperationHelperAsync( + nameof(ReadManyItemsAsync), + null, + (trace) => base.ReadManyItemsAsync(items, trace, readManyRequestOptions, cancellationToken)); + } + public override FeedIterator GetItemQueryStreamIterator( QueryDefinition queryDefinition, string continuationToken = null, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.ReadManyAsync.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.ReadManyAsync.xml new file mode 100644 index 0000000000..dcb3194b42 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.ReadManyAsync.xml @@ -0,0 +1,2301 @@ + + + + Read Many Stream Api + + + + + + + + + + Read Many Typed Api + feedResponse = await container.ReadManyItemsAsync(itemList); + ITrace trace = ((CosmosTraceDiagnostics)feedResponse.Diagnostics).Value; +]]> + + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs new file mode 100644 index 0000000000..9e778d9e00 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs @@ -0,0 +1,435 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Net; + using System.Net.Http; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Fluent; + using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class CosmosReadManyItemsTests : BaseCosmosClientHelper + { + private Container Container = null; + private ContainerProperties containerSettings = null; + + [TestInitialize] + public async Task TestInitialize() + { + await base.TestInit(); + string PartitionKey = "/pk"; + this.containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey); + ContainerResponse response = await this.database.CreateContainerAsync( + this.containerSettings, + throughput: 20000, + cancellationToken: this.cancellationToken); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Container); + Assert.IsNotNull(response.Resource); + this.Container = response; + + // Create items with different pk values + for (int i = 0; i < 500; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); + item.pk = "pk" + i.ToString(); + item.id = i.ToString(); + ItemResponse itemResponse = await this.Container.CreateItemAsync(item); + Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); + } + } + + [TestCleanup] + public async Task Cleanup() + { + await base.TestCleanup(); + } + + [TestMethod] + public async Task ReadManyTypedTest() + { + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i=0; i<10; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk" + i.ToString()))); + } + + FeedResponse feedResponse= await this.Container.ReadManyItemsAsync(itemList); + Assert.IsNotNull(feedResponse); + Assert.AreEqual(feedResponse.Count, 10); + Assert.IsTrue(feedResponse.Headers.RequestCharge > 0); + Assert.IsNotNull(feedResponse.Diagnostics); + + int count = 0; + foreach (ToDoActivity item in feedResponse) + { + count++; + Assert.IsNotNull(item); + } + Assert.AreEqual(count, 10); + } + + [TestMethod] + public async Task ReadManyStreamTest() + { + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk" + i.ToString()))); + } + + using (ResponseMessage responseMessage = await this.Container.ReadManyItemsStreamAsync(itemList)) + { + Assert.IsNotNull(responseMessage); + Assert.IsTrue(responseMessage.Headers.RequestCharge > 0); + Assert.IsNotNull(responseMessage.Diagnostics); + + ToDoActivity[] items = this.cosmosClient.ClientContext.SerializerCore.FromFeedStream( + CosmosFeedResponseSerializer.GetStreamWithoutServiceEnvelope(responseMessage.Content)); + Assert.AreEqual(items.Length, 5); + } + } + + [TestMethod] + public async Task ReadManyDoesNotFetchQueryPlan() + { + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk" + i.ToString()))); + } + + using (ResponseMessage responseMessage = await this.Container.ReadManyItemsStreamAsync(itemList)) + { + Assert.IsNotNull(responseMessage); + Assert.IsTrue(responseMessage.Headers.RequestCharge > 0); + Assert.IsNotNull(responseMessage.Diagnostics); + Assert.IsFalse(responseMessage.Diagnostics.ToString().Contains("Gateway QueryPlan")); + } + } + + [TestMethod] + public async Task ReadManyWithIdasPk() + { + string PartitionKey = "/id"; + ContainerProperties containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey); + Container container = await this.database.CreateContainerAsync(containerSettings); + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add((i.ToString(), new PartitionKey(i.ToString()))); + } + + // Create items with different pk values + for (int i = 0; i < 5; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); + item.id = i.ToString(); + ItemResponse itemResponse = await container.CreateItemAsync(item); + Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); + } + + using (ResponseMessage responseMessage = await container.ReadManyItemsStreamAsync(itemList)) + { + Assert.IsNotNull(responseMessage); + Assert.IsTrue(responseMessage.Headers.RequestCharge > 0); + Assert.IsNotNull(responseMessage.Diagnostics); + + ToDoActivity[] items = this.cosmosClient.ClientContext.SerializerCore.FromFeedStream( + CosmosFeedResponseSerializer.GetStreamWithoutServiceEnvelope(responseMessage.Content)); + Assert.AreEqual(items.Length, 5); + } + + FeedResponse feedResponse = await container.ReadManyItemsAsync(itemList); + Assert.IsNotNull(feedResponse); + Assert.AreEqual(feedResponse.Count, 5); + Assert.IsTrue(feedResponse.Headers.RequestCharge > 0); + Assert.IsNotNull(feedResponse.Diagnostics); + } + + [TestMethod] + public async Task ReadManyWithNestedPk() + { + string PartitionKey = "/NestedObject/pk"; + ContainerProperties containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey); + Container container = await this.database.CreateContainerAsync(containerSettings); + + // Create items with different pk values + for (int i = 0; i < 5; i++) + { + NestedToDoActivity item = NestedToDoActivity.CreateRandomNestedToDoActivity("pk" + i.ToString(), i.ToString()); + await container.CreateItemAsync(item); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk" + i.ToString()))); + } + + FeedResponse feedResponse = await container.ReadManyItemsAsync(itemList); + Assert.IsNotNull(feedResponse); + Assert.AreEqual(feedResponse.Count, 5); + Assert.IsTrue(feedResponse.Headers.RequestCharge > 0); + Assert.IsNotNull(feedResponse.Diagnostics); + } + + [TestMethod] + public async Task ValidateContainerRecreateScenario() + { + CosmosClient cc1 = TestCommon.CreateCosmosClient(); + CosmosClient cc2 = TestCommon.CreateCosmosClient(); + + Database database = null; + try + { + database = await cc1.CreateDatabaseAsync("ContainerRecreateScenarioDb"); + Container containerCC1 = await database.CreateContainerAsync("ContainerRecreateContainer", "/pk"); + + // Create items with different pk values + for (int i = 0; i < 5; i++) + { + ItemResponse itemResponse = await containerCC1.CreateItemAsync( + ToDoActivity.CreateRandomToDoActivity("pk" + i, i.ToString())); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk" + i))); + } + + FeedResponse feedResponse = await containerCC1.ReadManyItemsAsync(itemList); + Assert.AreEqual(feedResponse.Count, 5); + + Database databaseCC2 = cc2.GetDatabase("ContainerRecreateScenarioDb"); + Container containerCC2 = cc2.GetContainer("ContainerRecreateScenarioDb", "ContainerRecreateContainer"); + await containerCC2.DeleteContainerAsync(); + + // Recreate container + containerCC2 = await databaseCC2.CreateContainerAsync("ContainerRecreateContainer", "/pk"); + + // Check if recreate scenario works + feedResponse = await containerCC1.ReadManyItemsAsync(itemList); + Assert.AreEqual(feedResponse.Count, 0); + Assert.IsTrue(feedResponse.StatusCode == HttpStatusCode.OK); + } + finally + { + await database.DeleteAsync(); + cc1.Dispose(); + cc2.Dispose(); + } + } + + [TestMethod] + public async Task MultipleQueriesToSamePartitionTest() + { + for (int i = 0; i < 2500; i++) + { + await this.Container.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity("pk", i.ToString())); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 2500; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk"))); + } + + FeedResponse feedResponse = await this.Container.ReadManyItemsAsync(itemList); + Assert.AreEqual(feedResponse.Count, 2500); + } + + [TestMethod] + public async Task ReadMany404ExceptionTest() + { + Database database = await this.cosmosClient.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container container = await database.CreateContainerAsync(Guid.NewGuid().ToString(), "/pk"); + for (int i = 0; i < 5; i++) + { + await container.CreateItemAsync( + ToDoActivity.CreateRandomToDoActivity("pk" + i, i.ToString())); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add((i.ToString(), new PartitionKey("pk" + i))); + } + + // 429 test + //List tasks = new List(); + //ConcurrentQueue failedRequests = new ConcurrentQueue(); + //for (int i = 0; i < 500; i++) + //{ + // tasks.Add(Task.Run(async () => + // { + // ResponseMessage responseMessage = await container.ReadManyItemsStreamAsync(itemList); + // if (!responseMessage.IsSuccessStatusCode) + // { + // failedRequests.Enqueue(responseMessage); + // Assert.AreEqual(responseMessage.StatusCode, HttpStatusCode.TooManyRequests); + // } + // })); + //} + + //await Task.WhenAll(tasks); + //Assert.IsTrue(failedRequests.Count > 0); + + await container.ReadManyItemsAsync(itemList); // Warm up caches + + using (CosmosClient cosmosClient = TestCommon.CreateCosmosClient()) + { + Container newContainer = cosmosClient.GetContainer(database.Id, container.Id); + await newContainer.DeleteContainerAsync(); + } + + using (ResponseMessage responseMessage = await container.ReadManyItemsStreamAsync(itemList)) + { + Assert.AreEqual(responseMessage.StatusCode, HttpStatusCode.NotFound); + } + + try + { + await container.ReadManyItemsAsync(itemList); + Assert.Fail("Typed API should throw"); + } + catch (CosmosException ex) + { + Assert.AreEqual(ex.Error.Code, "NotFound"); + } + + await database.DeleteAsync(); + } + + [TestMethod] + [DataRow(HttpStatusCode.NotFound)] + public async Task ReadManyExceptionsTest(HttpStatusCode statusCode) + { + RequestHandler[] requestHandlers = new RequestHandler[1]; + requestHandlers[0] = new CustomHandler(statusCode); + + CosmosClientBuilder builder = TestCommon.GetDefaultConfiguration(); + builder.AddCustomHandlers(requestHandlers); + CosmosClient client = builder.Build(); + Database database = await client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container container = await database.CreateContainerAsync(Guid.NewGuid().ToString(), "/pk"); + for (int i = 0; i < 5; i++) + { + await container.CreateItemAsync( + ToDoActivity.CreateRandomToDoActivity("pk" + i, i.ToString())); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add(("IncorrectId" + i, new PartitionKey("pk" + i))); // wrong ids + } + + using (ResponseMessage responseMessage = await container.ReadManyItemsStreamAsync(itemList)) + { + Assert.AreEqual(responseMessage.StatusCode, statusCode); + } + + try + { + await container.ReadManyItemsAsync(itemList); + Assert.Fail("Typed API should throw"); + } + catch (CosmosException ex) + { + Assert.AreEqual(ex.StatusCode, statusCode); + } + + await database.DeleteAsync(); + client.Dispose(); + } + +#if PREVIEW + [TestMethod] + public async Task ReadManyMultiplePK() + { + IReadOnlyList pkPaths = new List { "/pk", "/description" }; + ContainerProperties containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPaths: pkPaths); + Container container = await this.database.CreateContainerAsync(this.containerSettings); + + for (int i = 0; i < 5; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); + item.pk = "pk" + i.ToString(); + item.id = i.ToString(); + item.description = "description" + i; + ItemResponse itemResponse = await container.CreateItemAsync(item); + Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + PartitionKey partitionKey = new PartitionKeyBuilder() + .Add("pk" + i) + .Add("description" + i) + .Build(); + + itemList.Add((i.ToString(), partitionKey)); + } + + FeedResponse feedResponse = await container.ReadManyItemsAsync(itemList); + Assert.IsNotNull(feedResponse); + Assert.AreEqual(feedResponse.Count, 5); + Assert.IsTrue(feedResponse.Headers.RequestCharge > 0); + Assert.IsNotNull(feedResponse.Diagnostics); + } +#endif + + private class NestedToDoActivity + { + public ToDoActivity NestedObject { get; set; } +#pragma warning disable IDE1006 // Naming Styles + public string id { get; set; } +#pragma warning restore IDE1006 // Naming Styles + + public static NestedToDoActivity CreateRandomNestedToDoActivity(string pk, string id) + { + return new NestedToDoActivity() + { + id = id, + NestedObject = ToDoActivity.CreateRandomToDoActivity(pk: pk) + }; + } + } + + private class CustomHandler: RequestHandler + { + private readonly HttpStatusCode statusCode; + + public CustomHandler(HttpStatusCode statusCode) + { + this.statusCode = statusCode; + } + + public override async Task SendAsync(RequestMessage requestMessage, + CancellationToken cancellationToken) + { + if (requestMessage.OperationType == Documents.OperationType.Query) + { + return new ResponseMessage(this.statusCode); + } + + return await base.SendAsync(requestMessage, cancellationToken); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 213f3d51f0..273a0fb996 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -31,6 +31,7 @@ + @@ -74,6 +75,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs index 7496635bd9..02194958a5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs @@ -959,6 +959,58 @@ public async Task MiscellanousAsync() this.ExecuteTestSuite(inputs); } + [TestMethod] + public async Task ReadManyAsync() + { + List inputs = new List(); + + int startLineNumber; + int endLineNumber; + + for (int i = 0; i < 5; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity("pk" + i, "id" + i); + await container.CreateItemAsync(item); + } + + List<(string, PartitionKey)> itemList = new List<(string, PartitionKey)>(); + for (int i = 0; i < 5; i++) + { + itemList.Add(("id" + i, new PartitionKey(i.ToString()))); + } + + //---------------------------------------------------------------- + // Read Many Stream + //---------------------------------------------------------------- + { + startLineNumber = GetLineNumber(); + ITrace trace; + using (ResponseMessage responseMessage = await container.ReadManyItemsStreamAsync(itemList)) + { + trace = responseMessage.Trace; + } + endLineNumber = GetLineNumber(); + + inputs.Add(new Input("Read Many Stream Api", trace, startLineNumber, endLineNumber)); + } + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + // Read Many Typed + //---------------------------------------------------------------- + { + startLineNumber = GetLineNumber(); + FeedResponse feedResponse = await container.ReadManyItemsAsync(itemList); + ITrace trace = ((CosmosTraceDiagnostics)feedResponse.Diagnostics).Value; + endLineNumber = GetLineNumber(); + + inputs.Add(new Input("Read Many Typed Api", trace, startLineNumber, endLineNumber)); + } + //---------------------------------------------------------------- + + this.ExecuteTestSuite(inputs); + } + public override Output ExecuteTest(Input input) { ITrace traceForBaselineTesting = CreateTraceForBaslineTesting(input.Trace, parent: null); @@ -1043,6 +1095,12 @@ private static JObject FindChild( private static void AssertTraceProperites(ITrace trace) { + if (trace.Name == "ReadManyItemsStreamAsync" || + trace.Name == "ReadManyItemsAsync") + { + return; // skip test for read many as the queries are done in parallel + } + if (trace.Children.Count == 0) { // Base case diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 086e541e87..6ad230afe6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -845,6 +845,11 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ContainerResponse] ReplaceContainerAsync(Microsoft.Azure.Cosmos.ContainerProperties, Microsoft.Azure.Cosmos.ContainerRequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.FeedResponse`1[T]] ReadManyItemsAsync[T](System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,Microsoft.Azure.Cosmos.PartitionKey]], Microsoft.Azure.Cosmos.ReadManyRequestOptions, System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.FeedResponse`1[T]] ReadManyItemsAsync[T](System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,Microsoft.Azure.Cosmos.PartitionKey]], Microsoft.Azure.Cosmos.ReadManyRequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ItemResponse`1[T]] CreateItemAsync[T](T, System.Nullable`1[Microsoft.Azure.Cosmos.PartitionKey], Microsoft.Azure.Cosmos.ItemRequestOptions, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], @@ -895,6 +900,11 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] ReadItemStreamAsync(System.String, Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ItemRequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] ReadManyItemsStreamAsync(System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,Microsoft.Azure.Cosmos.PartitionKey]], Microsoft.Azure.Cosmos.ReadManyRequestOptions, System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] ReadManyItemsStreamAsync(System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,Microsoft.Azure.Cosmos.PartitionKey]], Microsoft.Azure.Cosmos.ReadManyRequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] ReplaceContainerStreamAsync(Microsoft.Azure.Cosmos.ContainerProperties, Microsoft.Azure.Cosmos.ContainerRequestOptions, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], @@ -4682,6 +4692,51 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.ReadManyRequestOptions;Microsoft.Azure.Cosmos.RequestOptions;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel;CanRead:True;CanWrite:True;System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String get_SessionToken()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String get_SessionToken();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String SessionToken": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.String SessionToken;CanRead:True;CanWrite:True;System.String get_SessionToken();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_SessionToken(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(), Void .ctor()]" + }, + "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_SessionToken(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_SessionToken(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.Regions;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -5439,6 +5494,51 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.ReadManyRequestOptions;Microsoft.Azure.Cosmos.RequestOptions;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel;CanRead:True;CanWrite:True;System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] get_ConsistencyLevel();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String get_SessionToken()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String get_SessionToken();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String SessionToken": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.String SessionToken;CanRead:True;CanWrite:True;System.String get_SessionToken();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_SessionToken(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(), Void .ctor()]" + }, + "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_ConsistencyLevel(System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_SessionToken(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_SessionToken(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.Scripts.StoredProcedureRequestOptions;Microsoft.Azure.Cosmos.RequestOptions;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": {