Asp.net 如何从字符串解析ODataQueryOptions?

Asp.net 如何从字符串解析ODataQueryOptions?,asp.net,.net,entity-framework,entity-framework-6,odata,Asp.net,.net,Entity Framework,Entity Framework 6,Odata,我必须在ASP.NET API上为我们的EF6实体提供一些符合OData规范的读取端点。基于接受实例的函数,实体检索工作得很好 现在,根据文档,OData的实现不支持$count 然而,我们希望至少提供检索(过滤)数据集总计数的功能,如图所示,例如(通过稍微组合几个样本): (根据规范,我理解这应该是一个有效的、符合规范的OData查询,用于查询价格大于5的产品数量。如果我错了,请纠正我。) 现在,根据从ODataQuerySettings.ApplyTo返回的IQueryable检索计数是很简

我必须在ASP.NET API上为我们的EF6实体提供一些符合OData规范的读取端点。基于接受实例的函数,实体检索工作得很好

现在,根据文档,OData的实现不支持
$count

然而,我们希望至少提供检索(过滤)数据集总计数的功能,如图所示,例如(通过稍微组合几个样本):

(根据规范,我理解这应该是一个有效的、符合规范的OData查询,用于查询价格大于5的产品数量。如果我错了,请纠正我。)

现在,根据从
ODataQuerySettings.ApplyTo
返回的
IQueryable
检索计数是很简单的。捕获对此路由的请求也是如此:

[Route("$count({queryOptions})")]
public int Count(ODataQueryOptions<ProductEntity> queryOptions)
在这个方法中,
rawQueryOptions
包含我希望传递给OData的查询选项,即解析以填充
ODataQueryOptions
实例

这必须非常简单,因为任何其他OData端点都会发生同样的情况。作为比较:

[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
[路线(“”)]
公共IEnumerable筛选器(ODataQueryOptions查询选项)
这是有效的;查询选项按预期填充,与上面端点的情况不同

如何根据从路线提取的字符串填充OData查询选项实例?

我还尝试了一些东西:

  • [FromUri]
    应用于
    查询选项
    参数
  • [ODataQueryParameterBinding]
    应用于
    查询选项
    参数

尽管语法与您的请求略有不同,.Net OData实现具有您需要的OOTB支持,如果您提出这个问题,这表明您正在尝试将OData支持添加到标准API中


假设您已经在ASP.NETAPI上安装了EF6。。。为什么不在另一条路线上公开OData控制器?通过这种方式,您可以获得查询支持的完整实现,而无需解析
QueryOptions

更新 如果在单独的路由中添加新控制器不合适,您可以轻松升级现有的
ApiController
s,并实施OData路由来代替现有路由

继承自ApicController,但包含一些帮助器方法,这些方法简化了OData响应约定的使用,因此就地升级通常不会中断

例如,以下是允许所有受支持的OData查询选项从EF
DbSet
返回数据所需的唯一控制器代码,这包括对
$select
$expand
$filter
的完全支持,
$apply
甚至在嵌套的
$filter
中应用
$count

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);
    }
}
如何配置所需的$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中间件:


“为什么不公开另一条路线上的OData控制器并使其只读?”-这一决定不在我的直接影响范围内。你需要更具体一些,我建议你发布一个新问题,如果你
我必须提供一些读取端点,但你不能在另一条路线上创建新控制器。。。那你到底能做什么?如果无法修改API,则必须修改客户端;如果无法修改客户端,则唯一的其他选项是中继服务,该服务将预期调用转换为符合URL的URL。一般原则是,每个实体有一个基本路由。在该路径上,我们提供了一个接受OData参数进行过滤的GET端点。我可以在每个这样的实体路由上添加额外的端点(例如附加一个
/$count
),但我不能“突破”特定于实体的路由。现在我不太清楚您当时的问题是什么。我们的控制器应该继承自
ODataController
,这样您就可以获得ApiController的所有优点以及所有OData特性。不要尝试“插接”OData功能,您将在每个控制器中手动实现许多查询,这些查询已经为您烘焙到基本控制器和属性中。我将您的读取端点解释为“只读”,我将从回答中删除这些查询,因为这会误导您
[Route("")]
public IEnumerable<object> Filter(ODataQueryOptions<ProductEntity> queryOptions)
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);
    }
}
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);
}

/// <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);
    }
}
app.Use(typeof(ODataConventionUrlRewriter));