/// <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);
            }
        }
Beispiel #3
0
 public override IEnumerable <Projection <TValue> > Accept <TValue>(ProjectionVisitor <TValue> visitor, ProjectionVisitor <TValue> .Arguments args, GraphDirection direction) => visitor.Visit(this, args, direction);