尽管语法与您的请求略有不同,但 .Net OData 实现具有您需要的 OOTB 支持,如果您确实问这个问题,则表明您正在尝试向标准 API 添加 OData 支持。
鉴于您已经在 ASP.Net API 上安装了 EF6...为什么不在另一条路线上公开 OData 控制器呢?通过这种方式,您可以获得查询支持的完整实现,而无需解析QueryOptions
at all.
Updated
如果不适合在单独的路线中添加新控制器,您可以轻松升级现有控制器ApiController
并实施 OData 路由来代替现有路由。
ODataController https://learn.microsoft.com/en-us/previous-versions/aspnet/jj890597(v=vs.118)继承自 ApiController,但包含一些简化使用 OData 响应约定的辅助方法,因此就地升级通常不会造成破坏。
作为示例,以下是允许所有受支持的 OData 查询选项从 EF 返回数据所需的唯一控制器代码DbSet
,这包括全力支持$select
, $expand
, $filter
, $apply
乃至$count
跨越嵌套$filter
.
public partial class ResidentsController : ODataController
{
protected MyEF.Context db = new MyEF.Context();
[EnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<MyEF.Resident> options)
{
return Ok(db.Residents);
}
}
允许这种情况发生的魔法并不是ODataController
本身,EnableQueryAttribute
解析/翻译QueryOptions
并将其应用到实体连接从方法返回的表达式。
完成这项工作的最后一个组件是注册路由,这比标准 API 更复杂一些,因为您需要定义EdmModel
首先,但这样做我们永远不需要解析传入的查询参数。
为上述控制器配置模型和路由的最小示例可能如下所示:
public static void Register(HttpConfiguration config)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Resident>("Residents");
IEdmModel model = builder.GetEdmModel();
// To enable $select and $filter on all fields by default
config.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
// can also be configured like this
config.SetDefaultQuerySettings(new Microsoft.AspNet.OData.Query.DefaultQuerySettings()
{
EnableCount = true,
EnableExpand = true,
EnableFilter = true,
EnableOrderBy = true,
EnableSelect = true,
MaxTop = null
});
// Map the routes from the model using OData Conventions
config.MapODataServiceRoute("odata", "odata", model);
}
如何配置您想要的 $count 语法
虽然 OOTB 不支持您预期的过滤集合计数语法,但支持的语法非常接近,因此您可以使用 URL 重写模块轻松操作查询
- 您期望的语法:
http://host/service/Products/$count($filter=Price gt 5.00)
- .Net 支持的语法
http://host/service/Products/$count?$filter=Price gt 5.00
Owin中间件:
/// <summary>
/// Rewrite incoming OData requests that are implemented differently in the .Net pipeline
/// </summary>
public class ODataConventionUrlRewriter : OwinMiddleware
{
private static readonly PathString CountUrlSegments = PathString.FromUriComponent("/$count");
public ODataConventionUrlRewriter(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
// OData spec says this should work: http://host/service/Products/$count($filter=Price gt 5.00)
// But in .Net the filter needs to be in the query: http://host/service/Products/$count?$filter=Price gt 5.00
var regex = new System.Text.RegularExpressions.Regex(@"\/\$count\((.+)\)$");
var match = regex.Match(context.Request.Path.Value);
if(match != null && match.Success)
{
// So move the $filter expression to a query option
// We have to use redirect here, we can't affect the query inflight
context.Response.Redirect($"{context.Request.Uri.GetLeftPart(UriPartial.Authority)}{regex.Replace(context.Request.Path.Value, "/$count")}?{match.Groups[1].Value}");
}
else
await Next.Invoke(context);
}
}
在注册 OData 路由之前添加到 Startup.cs
app.Use(typeof(ODataConventionUrlRewriter));