我有一个WebApi方法,该方法返回RavenDB文档的IQueryable。调用者需要知道可能结果的数量(因为实际结果受到限制/分页)。

因此,我的WebApi方法结尾处有类似以下内容:

HttpContext.Current.Response.AddHeader("Total-Result-Count",
    resultsStats.TotalResults.ToString())

不幸的是,这将无法正常工作,因为IQueryable尚未实际执行-因此统计信息将为空。

在查询执行之后,如何将统计信息响应 header 的填充推迟?

[更新]

我试图在执行 Controller Action 后应用ActionFilter来捕获结果...但是似乎在实际枚举IQueryable之前调用了ActionFilter ...
public class CountQueryableResultsActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext filterContext)
    {
        var controllerStats = filterContext.ActionContext.ControllerContext.Controller as IControllerStatistics;
        System.Web.HttpContext.Current.Response.AddHeader("Total-Result-Count", controllerStats.TotalResults.ToString());
    }
}

如果在WebApi方法的末尾调用了“IQueryable.ToArray()”,则Linq查询将立即执行,生成统计信息,并且一切正常-但这将阻止用户能够应用自己的OData过滤器等等...

最佳答案

好的-我知道了。

以下内容将导致仅发出一个Raven查询,该查询同时返回结果和结果计数。

感谢David Ruttka在这方面的实验。我已经修改了他的代码以与RavenDb一起使用。此代码将按照RavenDB的意图通过一个数据库查询返回结果和结果计数。

我在下面附加了我的代码-要使用此代码,您必须从WebApi方法返回IRavenQueryable<T>(而不是IQueryable<T>)。然后,将$ inlinecount = allpages附加到您的Uri中将调用该处理程序。此代码不会破坏其他OData查询扩展($ take,$ skip等)

注意:此代码使用“内联”技术,因为统计信息在邮件正文中返回-如果愿意,您可以更改代码以将统计信息注入(inject) header 中-我只是选择采用OData工作的标准方式。

您可以修改此代码以包括Raven生成的所有统计信息。

使用以下代码向ASP.NET注册处理程序(在Global.asax.cs中)

注册码:
GlobalConfiguration.Configuration.MessageHandlers.Add(new WebApi.Extensions.InlineRavenCountHandler());
处理程序代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Reflection;
using System.Net.Http.Headers;
using System.Net;

namespace WebApi.Extensions
{
    public class InlineRavenCountHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (!ShouldInlineCount(request))
                return base.SendAsync(request, cancellationToken);

            // Otherwise, we have a continuation to work our magic...
            return base.SendAsync(request, cancellationToken).ContinueWith(
                t =>
                {
                    var response = t.Result;

                    // Is this a response we can work with?
                    if (!ResponseIsValid(response)) return response;

                    var pagedResultsValue = this.GetValueFromObjectContent(response.Content);
                    Type queriedType;

                    // Can we find the underlying type of the results?
                    if (pagedResultsValue is IQueryable)
                    {
                        queriedType = ((IQueryable)pagedResultsValue).ElementType;

                        // we need to work with an instance of IRavenQueryable to support statistics
                        var genericQueryableType = typeof(Raven.Client.Linq.IRavenQueryable<>).MakeGenericType(queriedType);

                        if (genericQueryableType.IsInstanceOfType(pagedResultsValue))
                        {
                            Raven.Client.Linq.RavenQueryStatistics stats = null;

                            // register our statistics object with the Raven query provider.
                            // After the query executes, this object will contain the appropriate stats data
                            dynamic dynamicResults = pagedResultsValue;
                            dynamicResults.Statistics(out stats);


                            // Create the return object.
                            var resultsValueMethod =
                                this.GetType().GetMethod(
                                    "CreateResultValue", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(
                                        new[] { queriedType });

                            // Create the result value with dynamic type
                            var resultValue = resultsValueMethod.Invoke(
                                this, new[] { stats, pagedResultsValue });

                            // Push the new content and return the response
                            response.Content = CreateObjectContent(
                                resultValue, response.Content.Headers.ContentType);
                            return response;

                        }
                        else
                            return response;
                    }
                    else
                        return response;
               });
        }

        private bool ResponseIsValid(HttpResponseMessage response)
        {
            // Only do work if the response is OK
            if (response == null || response.StatusCode != HttpStatusCode.OK) return false;

            // Only do work if we are an ObjectContent
            return response.Content is ObjectContent;
        }

        private bool ShouldInlineCount(HttpRequestMessage request)
        {
            var queryParams = request.RequestUri.ParseQueryString();
            var inlinecount = queryParams["$inlinecount"];
            return string.Compare(inlinecount, "allpages", true) == 0;
        }

    // Dynamically invoked for the T returned by the resulting ApiController
    private ResultValue<T> CreateResultValue<T>(Raven.Client.Linq.RavenQueryStatistics stats, IQueryable<T> pagedResults)
    {
        var genericType = typeof(ResultValue<>);
        var constructedType = genericType.MakeGenericType(new[] { typeof(T) });

        var ctor = constructedType
            .GetConstructors().First();

        var instance = ctor.Invoke(null);

        var resultsProperty = constructedType.GetProperty("Results");
        resultsProperty.SetValue(instance, pagedResults.ToArray(), null);

        var countProperty = constructedType.GetProperty("Count");
        countProperty.SetValue(instance, stats.TotalResults, null);

        return instance as ResultValue<T>;
    }

        // We need this because ObjectContent's Value property is internal
        private object GetValueFromObjectContent(HttpContent content)
        {
            if (!(content is ObjectContent)) return null;

            var valueProperty = typeof(ObjectContent).GetProperty("Value", BindingFlags.Instance | BindingFlags.NonPublic);
            if (valueProperty == null) return null;

            return valueProperty.GetValue(content, null);
        }

        // We need this because ObjectContent's constructors are internal
        private ObjectContent CreateObjectContent(object value, MediaTypeHeaderValue mthv)
        {
            if (value == null) return null;

            var ctor = typeof(ObjectContent).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
                ci =>
                {
                    var parameters = ci.GetParameters();
                    if (parameters.Length != 3) return false;
                    if (parameters[0].ParameterType != typeof(Type)) return false;
                    if (parameters[1].ParameterType != typeof(object)) return false;
                    if (parameters[2].ParameterType != typeof(MediaTypeHeaderValue)) return false;
                    return true;
                });

            if (ctor == null) return null;

            return ctor.Invoke(new[] { value.GetType(), value, mthv }) as ObjectContent;
        }
    }

    public class ResultValue<T>
    {
        public int Count { get; set; }
        public T[] Results { get; set; }
    }
}

关于ravendb - 返回IQueryable,但需要Raven统计信息才能插入TotalResults header ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/10544377/

10-13 03:46