EF Core return IQueryable 可以来自存储过程/视图/Table 值函数吗?

Can EF Core return IQueryable from Stored Procedure / Views / Table Valued Function?

我们需要将 ODATA-V4 query search, order by 子句直接传递给数据库。

情况如下:

  1. table 之间存在连接,我们调用(内联)table 值 使用 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;**
            }
        }

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

Can we configure EF Core 2.2 to return IQueryable so that ODATA could further filter out and then yield only desired counts say 10.?

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

我认为你真正问的是,你是否可以在 database-level 进一步查询,这样你所追求的最终结果集就是 return从数据库中编辑。答案是否定的。必须首先评估存储过程。完成后,所有结果都已从数据库中 returned。您可以在内存中进一步过滤,但对于数据库来说已经太晚了。

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

EF 无法从存储过程 return IQueryable,因为数据库引擎本身不提供 selectively 查询或操纵执行的机制脚本,例如,您不能在 SQL:

中执行以下操作
SELECT Field1, Field2
EXEC dbo.SearchForData_SP()
WHERE Field2 IS NOT NULL
ORDER BY Field3

存储过程是引擎的黑匣子,正因为如此,您可以在 SP 中使用某些类型的表达式和操作,而这些表达式和操作在正常情况下无法使用设置基于 SQL 的查询或表达式。例如,您可以执行其他存储过程。 SP 必须完整执行,然后才能处理结果。

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

  1. There are joins among tables and we invoke (inline) table valued functions using SQL to get desired records.

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

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

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

    • 您仍然可以将响应包装在 IQueryable<T> 中并使用 EnableQuery 属性修饰此操作,这将在内存中执行 $select、$expand 和简单的 $filter 操作但该服务仍会在构建响应负载之前将 整个记录集 加载到内存中。这种机制仍然可以减少服务器和客户端之间的带宽,只是不会减少数据库和服务层之间的带宽。
    • 如果您需要针对不同的用例使用不同的结果结构,请制作不同版本的 SP。
  2. 仅当查询过于复杂而无法使用 linq 或您需要使用 Table 提示CTE递归 CTEWindow 无法在 Linq 中轻松复制的函数

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

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

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