Ada*_*dam 6 iqueryable ravendb wcf-web-api asp.net-web-api
我有一个WebApi方法,它返回一个IQueryable的RavenDB文档.调用者需要知道可能结果的数量(因为实际结果是有限的/分页的).
所以,我的WebApi方法结束时有类似的东西:
HttpContext.Current.Response.AddHeader("Total-Result-Count",
resultsStats.TotalResults.ToString())
Run Code Online (Sandbox Code Playgroud)
不幸的是,这不起作用,因为IQueryable还没有实际执行 - 所以统计数据将是空的.
在查询执行之后,如何推迟 stats响应标头的填充?
[UPDATE]
在控制器操作执行后,我尝试应用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());
}
}
Run Code Online (Sandbox Code Playgroud)
如果,我在WebApi方法结束时调用了"IQueryable.ToArray()",那么Linq查询会立即执行,它会生成统计信息,一切正常 - 但这会阻止用户应用自己的OData过滤器等等...
好吧 - 我想通了。
以下将导致仅发出单个 Raven 查询,该查询同时返回结果和结果计数。
感谢David Ruttka在该领域的实验。我已经调整了他的代码以与 RavenDb 一起使用。正如 RavenDB 的预期,此代码将通过一个数据库查询返回结果和结果计数。
我在下面附加了我的代码 - 要使用它,您必须IRavenQueryable<T>从 WebApi 方法返回(而不是IQueryable<T>)。然后,将 $inlinecount=allpages 附加到您的 Uri 将调用处理程序。此代码不会破坏其他 OData 查询扩展($take、$skip 等)
注意:此代码使用“内联”技术,因为统计信息在消息正文中返回 - 如果您愿意,您可以更改代码以将统计信息注入标头 - 我只是选择采用 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; }
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1512 次 |
| 最近记录: |