我有一个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/