【问题标题】:How do I ensure an error status code for errors when returning an IQueryable from a Web API method?从 Web API 方法返回 IQueryable 时,如何确保错误的错误状态代码?
【发布时间】:2019-01-04 10:47:40
【问题描述】:

我通过 Microsoft.AspNetCore.OData 包开始使用 OData。成功案例正在运行,但我注意到当 IQueryable<T> 成功返回但无法执行时,我没有收到一个像样的错误消息。相反,我得到了一个截断的结果:

{"@odata.context":"https://localhost:44300/odata/$metadata#Documents","value":[

我的控制器没有什么特别之处:

public class DocumentsController : ODataController
{
    Db.Context Context { get; }

    public DocumentsController(Db.Context context)
    {
        Context = context;
    }

    [EnableQuery]
    public ActionResult<IQueryable<Document>> Get()
    {
        return Context.Documents;
    }
}

问题不是 OData 特定的:返回 IQueryable&lt;Document&gt; 的普通 ControllerBase 派生控制器的行为方式相同(除了截断的结果仅为 [)。但是,OData 的上下文可能会排除一些可能的解决方案。

我明白为什么会这样:发送序列化结果已经开始;无法及时返回以取消发送并发送错误响应。

我也明白,在一般情况下,阻止不是一个真正的选择:当发送许多项目时,可能会发送除最后一个之外的所有项目而没有任何问题,并且可能会在最后一个项目上引发一些异常,所以在一般情况下防止它需要缓冲完整的结果。

但是,至少在IQueryable&lt;T&gt; 返回其第一个结果之前,应该可以处理引发异常的情况。这将涵盖最严重的错误,其中良好的异常消息对调试非常有帮助:数据库脱机、数据库架构与预期不符等。

我该怎么做?

【问题讨论】:

    标签: .net asp.net-core odata asp.net-core-webapi iqueryable


    【解决方案1】:

    您可以直接在控制器中调用查询选项,而不是使用 [Queryable] 属性。它允许您执行任何您想要的异常处理,并在需要时构建自定义响应。

    为此,将 ODataQueryOptions 参数添加到控制器方法。在这种情况下,您不需要 [Queryable] 属性。更多详情https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly

    【讨论】:

    • 我试了一下,它可能是解决方案的一部分(谢谢),但它本身并没有帮助。它仍然涉及返回一个IQueryable&lt;Document&gt;,如果该可查询的执行失败,部分响应仍然已经发送。您的建议将允许我将返回类型更改为 List&lt;Document&gt; 并返回 query.ToList(),这确实可以更好地处理异常,但这涉及再次将完整集加载​​到内存中,这与缓冲完整结果的原因相同。
    • 查询结果无论如何都会被加载到内存中,或者通过调用 ToList() 或者进一步通过 OData 处理程序。以防万一:应用查询选项后应调用 ToList() 以确保仅加载所需的结果页面(而不是完整的数据库)
    • 不应将完整的查询结果加载到内存中。当然,每个单独的查询结果都需要加载到内存中,但不是同时加载(ToList() 确实强制)。一旦第一个查询结果被流式传输,它就可以被丢弃。然后可以加载、流式传输和丢弃第二个查询结果,依此类推。这就是您执行简单的foreach (var item in query) { ... } 时会发生的情况,我有一个强烈的印象,即IQueryable&lt;T&gt; 的默认序列化也会发生这种情况。
    • 非常有趣的假设。根据它,如果您从数据库foreach (var item in query) 请求 10 个条目,将一一加载它们以一次仅为 1 个条目分配内存?在这种情况下,对数据库的请求数是多少?
    • 这是一个单一的请求,响应被流式传输并且一次读取一条记录是正常的:这就是DbDataReader 的工作方式。将它变成一些花哨的IQueryable&lt;T&gt; 并按需构造DbDataReader 的包装器可以保留这种行为,只要您注意不要将实体加载到数据上下文中,EF 就会这样做。
    【解决方案2】:

    我可以创建一个自定义的IEnumerable&lt;T&gt; 实现,它包装另一个IEnumerable&lt;T&gt;,并在构造时立即调用.GetEnumerator().MoveNext(),注意保存枚举器和MoveNext() 响应。然后,当第一次调用 my GetEnumerator() 时,我返回一个已保存枚举数的包装器。第一次调用它的MoveNext() 时,我返回保存的布尔值。其他用途只是向前包装的可枚举和包装的枚举器。我会省略代码,因为实现几乎是微不足道的。

    棘手的部分是找到包装可查询对象的正确点。它需要在 URL 中的任何查询修饰符被应用之后。这里有两个主要选项:

    1. 忘记EnableQueryAttribute。相反,正如 Ihar Yakimush 回答的那样,我可以采用 ODataQueryOptions&lt;Document&gt; 参数并直接应用查询修饰符。由于在修改查询后我仍然可以控制,因此我可以将其包装在我的自定义枚举中。

    2. 子类EnableQueryAttribute 并覆盖其OnResultExecuting(ResultExecutingContext context) 方法。然后,我可以检查是否context.Result is ObjectResult,如果是,如果objectResult.ValueIEnumerable&lt;T&gt;。如果两者都是真的,那么我可以包装结果。

      这种方法的一种变体是将OnResultExecuting 放在单独的ActionFilterAttribute 派生类中,但对我来说将它们放在同一个类中更有意义。

    在这两种情况下,我都必须处理我没有得到IQuerable&lt;Document&gt; 的情况:我可能会得到IQueryable&lt;SomeWrapperAround&lt;Document&gt;&gt;。处理这很容易。 dynamic 似乎会变得异常简单:只需创建 object Wrap(object o) =&gt; oobject Wrap&lt;T&gt;(IQueryable&lt;T&gt; queryable) =&gt; new EnumerableWrapper&lt;T&gt;(queryable) 重载并让运行时确定要调用的方法,但不幸的是 SomeWrapperAround 可以是 OData 内部类,@987654342 @不喜欢。手动反射还是不太难的。

    【讨论】:

      猜你喜欢
      • 2014-10-26
      • 2018-04-27
      • 2012-11-14
      • 2021-07-27
      • 1970-01-01
      • 1970-01-01
      • 2017-05-17
      • 2016-08-02
      • 2015-03-21
      相关资源
      最近更新 更多