EF核心能否从存储过程/视图/表值函数中返回IQueryable?

时间:2019-07-16 11:45:11

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

我们需要传递ODATA-V4查询搜索,将子句直接排序到数据库。

是这种情况:

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

我们从Dapper开始,但是Dapper仅支持IEnumerable,因此Dapper将从数据库中带走整个记录,然后仅应用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;**
            }
        }

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

  

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

2 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:0)

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 {1}}不会改变在该List对象中维护数据的事实。

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

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

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

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

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

    • 在许多情况下,使用CTE(非递归)时,可以在Linq中更容易构造该表达式。
    • 要从索引中获取最大性能,可以在SQL中使用表提示,因为我们无法严格控制Linq表达式如何组合成SQL,因此可能需要花费一定的时间。以数据库可以为我们优化查询的方式来构造一些查询的大量工作。在许多情况下,如上述CTE一样,在Linq中重写查询过程可以帮助避免传统上使用表提示的情况。

      • 使用EF不支持的专用SQL Server概念进行控制时,存在局限性,这是您在有意识地做出选择而不是选择一个的决定。

我不同意OData和存储过程在根本上是不兼容的在许多用例中,两者确实相处得很好,但是您必须找到平衡点。如果您觉得有必要将$ select,$ expand,$ filter,$ top,$ skip ...等查询选项传递给存储过程,请更改您的实现,使其仅在Views中构造(因此, SP)或更改客户端实现,以便您传递可以直接在SP中处理的形式参数。