【问题标题】:Can EF Core return IQueryable from Stored Procedure / Views / Table Valued Function?EF Core 可以从存储过程/视图/表值函数返回 IQueryable 吗?
【发布时间】:2019-07-16 11:45:11
【问题描述】:

我们需要将 ODATA-V4 查询搜索、order by 子句直接传递给数据库。

情况如下:

  1. 表之间存在连接,我们调用(内联)表值 使用 SQL 获取所需记录的函数。
  2. ODATA where 子句需要应用于结果集,然后我们 应用分页 Skip、Take 和 Order By。

我们从 Dapper 开始,但是 Dapper 仅支持 IEnumerable,因此 Dapper 会从 DB 中获取整个记录,然后仅应用 OData(查询选项 ApplyTo)分页,从而破坏了性能提升 :-(

        [ODataRoute("LAOData")]
        [HttpGet]
        public IQueryable<LAC> GetLAOData(ODataQueryOptions<LAC> queryOptions)
        {
            using (IDbConnection connection = new SqlConnection(RoutingConstants.CascadeConnectionString))
            {
                var sql = "<giant sql query";
                IQueryable<LAC> iqLac = null;
                IEnumerable<LAC> sqlRes = connection.Query<LAC>(sql, commandTimeout: 300);
                **IQueryable<LAC> iq = sqlRes.AsQueryable();
                iqLac = queryOptions.ApplyTo(iq) as IQueryable<LAC>;
                return iqLac;**
            }
        }

我们在存储过程中看到的大多数示例,视图支持显然返回列表。 https://hackernoon.com/execute-a-stored-procedure-that-gets-data-from-multiple-tables-in-ef-core-1638a7f010c

我们能否将 EF Core 2.2 配置为返回 IQueryable 以便 ODATA 可以 进一步过滤掉,然后只产生所需的计数,比如 10。?

【问题讨论】:

标签: c# entity-framework-core odata asp.net-core-2.2 ef-core-2.2


【解决方案1】:

嗯,是的,也不是。您当然可以返回IQueryable,而且您似乎已经这样做了。而且,您当然可以通过 LINQ 进一步查询 IQueryble在内存中

我认为您真正要问的是,您是否可以在数据库级别进一步查询,这样只有您所追求的最终结果集才会从数据库返回。答案是肯定的。必须首先评估存储过程。完成后,所有结果都已从数据库返回。您可以在内存中进一步过滤,但对于数据库来说已经太迟了。

也就是说,您应该了解 OData 从根本上与使用诸如存储过程之类的东西的想法不兼容。重点是通过 URL 参数描述查询 - 整个查询。您可以改用视图,但存储过程不应与 OData 一起使用。

【讨论】:

  • 感谢 Chris,我更倾向于采用 Views 路径 --- 如果我们使用 Views,我们能否在数据库级别进一步查询,使用 ODATA 运算符?
  • 是的。从 EF 的角度来看,视图只是另一个表。
【解决方案2】:

EF 无法从存储过程返回 IQueryable,因为数据库引擎本身不提供选择性查询或操作脚本执行的机制,例如,您不能在 SQL 中执行以下操作:

SELECT Field1, Field2
EXEC dbo.SearchForData_SP()
WHERE Field2 IS NOT NULL
ORDER BY Field3

存储过程对引擎来说是一个黑盒,因此,您可以在 SP 中使用某些类型的表达式和操作,而这些类型的表达式和操作无法在基于普通集合的 SQL 查询或表达式。例如,您可以执行其他存储过程。必须先完整执行 SP,然后才能处理结果。

如果数据库引擎本身无法优化存储过程的执行,那么你的 ORM 框架将很难做到这一点。 这就是为什么大多数关于通过 EF 执行 SP 的文档和示例都返回 List,因为这清楚地表明该列表的全部内容都在内存中,将 List 转换为 IQueryable.AsQueryable() 不会改变数据保存在该 List 对象中的事实。

  1. 表之间存在连接,我们使用 SQL 调用(内联)表值函数来获取所需的记录。

您在此处描述的内容类似于 OData 和 EF 尝试为您提供的内容,即用于编写复杂查询的机制。要充分利用 OData 和 EF,您应该考虑使用 linq 语句复制或替换 TVF。 EF 与RDBMS 无关,因此它尝试使用和执行可应用于许多数据库引擎的通用标准,而不仅仅是 SQLSERVER。当涉及到 CTE、TVF 和 SP 时,每个数据库引擎中的实现和语法变得更加具体,在某些情况下甚至针对特定版本。 EF 团队不必试图成为所有人的一切,而是必须强制执行一些限制,以便他们能够保持为我们提供的服务的质量。

然而,有一个快乐的媒介可以实现,您可以利用这两个引擎的力量:

  1. 设计您的 SP,以便过滤变量作为参数传递,并将对存储过程的依赖限制在输出结构与您通常需要的一样高效的情况下。然后,您可以将 SP 公开为 OData 中的 Action 端点,并且被调用者可以将参数值直接传递给 SP。

    • 您仍然可以将响应包装在IQueryable&lt;T&gt; 中并使用EnableQuery 属性装饰此操作,这将在内存中执行$select、$expand 和简单的$filter 操作,但服务仍将加载在构造响应负载之前将整个记录集放入内存。这种机制仍然可以减少服务器和客户端之间的带宽,只是不能减少数据库和服务层之间的带宽。
    • 如果您需要为不同的用例提供不同的结果结构,请制作不同版本的 SP。
  2. 仅当查询过于复杂而无法使用 linq 轻松表达或需要使用表格提示CTETVFs 或Views /em>、递归 CTE窗口函数,在 Linq 中无法轻松复制。

    • 在许多使用 CTE(非递归)的情况下,表达式可以更容易在 Linq 中构造。
    • 要从索引中获得最大性能,您可以在 SQL 中使用 Table Hints,因为我们无法严格控制 Linq 表达式将如何组合到 SQL 中,这可能需要大量工作以数据库可以为我们优化它们的方式构建一些查询。在许多情况下,与上面的 CTE 一样,在 Linq 中重写查询可以帮助避免传统上使用表提示的情况。

      • 存在限制,当您想要或需要控制时,使用 EF 不支持的专用 SQL Server 概念时,您有意识地决定拥有一个而不是另一个。

我不同意 OData 和存储过程从根本上不兼容在许多用例中两者相处得很好,但你必须找到平衡点。如果您觉得需要将 $select、$expand $filter、$top、$skip... 等查询选项传递给您的存储过程,或者将您的实现更改为纯粹在 Linq 中构建(所以没有 SP)或更改客户端实现,以便您传递可以直接在 SP 中处理的形式参数。

【讨论】:

    猜你喜欢
    • 2010-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 2017-10-11
    • 2020-05-12
    相关资源
    最近更新 更多