/// <inheritdoc/> public override FieldMiddleware CreateExecutor <TEntityType>() { return(next => context => ExecuteAsync(next, context)); async ValueTask ExecuteAsync( FieldDelegate next, IMiddlewareContext context) { // first we let the pipeline run and produce a result. await next(context).ConfigureAwait(false); if (context.Result is not null) { var visitorContext = new MongoDbProjectionVisitorContext(context, context.ObjectType); var visitor = new ProjectionVisitor <MongoDbProjectionVisitorContext>(); visitor.Visit(visitorContext); if (!visitorContext.TryCreateQuery( out MongoDbProjectionDefinition? projections) || visitorContext.Errors.Count > 0) { context.Result = Array.Empty <TEntityType>(); foreach (IError error in visitorContext.Errors) { context.ReportError(error.WithPath(context.Path)); } } else { context.LocalContextData = context.LocalContextData.SetItem( nameof(ProjectionDefinition <TEntityType>), projections); await next(context).ConfigureAwait(false); if (context.Result is IMongoDbExecutable executable) { context.Result = executable.WithProjection(projections); } } } }
public async IAsyncEnumerator <T> GetAsyncEnumerator(CancellationToken cancellation = default) { var query = (DataServiceQuery) new DataServiceContext(account.TableEndpoint).CreateQuery <T>(tableName) .Provider.CreateQuery(expression); // OData will translate the enum value in a filter to TYPENAME.'ENUMVALUE'. // The type name can contain the + sign if it's a nested type, too. So we // need to remove the type name plus the dot and just leave the string // value as part of the filter string. var rawqs = Regex.Replace( query.RequestUri.GetComponents(UriComponents.Query, UriFormat.Unescaped), "(\\W)[\\w\\+\\.]+('\\w+')", "$1$2"); // We need to count & break manually because $top is interpreted as the max records per page // if the set matches more items. This is clearly unintuitive and *not* what one typically // wants when using LINQ queries! See Note on https://docs.microsoft.com/en-us/rest/api/storageservices/querying-tables-and-entities#supported-query-options var qs = HttpUtility.ParseQueryString(rawqs); if (!long.TryParse(qs["$top"], out var top)) { top = -1; } long count = 0; // Covers weird case of top == 0. if (count == top) { yield break; } if (qs["$select"] != null) { // Collect the properties being projected, and append the built-in ones to them. var projection = new ProjectionVisitor(); projection.Visit(expression); if (projection.Properties.Count > 0) { qs["$select"] = string.Join(",", projection.Properties // NOTE: skip key properties since they are renamed back before deserialization below .Where(prop => prop != partitionKeyProperty && prop != rowKeyProperty) // NOTE: always project the built-in props. .Concat(new[] { nameof(ITableEntity.PartitionKey), nameof(ITableEntity.RowKey), nameof(ITableEntity.ETag), nameof(ITableEntity.Timestamp) })); } } var filter = qs["$filter"]; if (filter != null && (partitionKeyProperty != null || rowKeyProperty != null)) { if (partitionKeyProperty != null) { filter = Regex.Replace(filter, partitionKeyProperty + "(\\W)", "PartitionKey$1"); } if (rowKeyProperty != null) { filter = Regex.Replace(filter, rowKeyProperty + "(\\W)", "RowKey$1"); } qs["$filter"] = filter; } if (PartitionKey != null) { if (filter == null) { filter = "PartitionKey eq '" + PartitionKey + "'"; } else { filter = "PartitionKey eq '" + PartitionKey + "' and " + filter; } qs["$filter"] = filter; } var builder = new UriBuilder(query.RequestUri) { Query = string.Join("&", qs.AllKeys.Select(x => $"{x}={qs[x]}")) }; var request = new HttpRequestMessage(HttpMethod.Get, builder.Uri) .AddAuthorizationHeader(account); var response = await Http.Client.SendAsync(request); while (true) { response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); var doc = JsonDocument.Parse(json); foreach (var element in doc.RootElement.GetProperty("value").EnumerateArray()) { var mem = new MemoryStream(); var writer = new Utf8JsonWriter(mem); writer.WriteStartObject(); // Write the renamed key properties, if any. if (partitionKeyProperty != null) { writer.WriteString(partitionKeyProperty, element.GetProperty("PartitionKey").GetString()); } if (rowKeyProperty != null) { writer.WriteString(rowKeyProperty, element.GetProperty("RowKey").GetString()); } foreach (var property in element.EnumerateObject()) { property.WriteTo(writer); } writer.WriteEndObject(); writer.Flush(); var data = Encoding.UTF8.GetString(mem.ToArray()); var item = serializer.Deserialize <T>(data); if (item != null) { yield return(item); count++; if (count == top) { yield break; } } } if (!response.Headers.TryGetValues("x-ms-continuation-NextPartitionKey", out var nextPartitionKeyValues) || nextPartitionKeyValues.FirstOrDefault() is not string nextPartitionKey || string.IsNullOrEmpty(nextPartitionKey) || !response.Headers.TryGetValues("x-ms-continuation-NextRowKey", out var nextRowKeyValues) || nextRowKeyValues.FirstOrDefault() is not string nextRowKey || string.IsNullOrEmpty(nextRowKey)) { break; } qs["NextPartitionKey"] = nextPartitionKey; qs["NextRowKey"] = nextRowKey; builder.Query = string.Join("&", qs.AllKeys.Select(x => $"{x}={qs[x]}")); request = new HttpRequestMessage(HttpMethod.Get, builder.Uri) .AddAuthorizationHeader(account); response = await Http.Client.SendAsync(request); } }
public override IEnumerable <Projection <TValue> > Accept <TValue>(ProjectionVisitor <TValue> visitor, ProjectionVisitor <TValue> .Arguments args, GraphDirection direction) => visitor.Visit(this, args, direction);