【问题标题】:Entity Framework: Precompiled Query for Enumerable.Contains实体框架:Enumerable.Contains 的预编译查询
【发布时间】:2016-01-14 08:31:33
【问题描述】:

Entity Framework 5+ 应该预编译所有查询。但是,对于诸如

之类的查询
List<Guid> ids;
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray();

Entity Framework 无法对查询进行预编译,根据整体查询的复杂程度,将表达式树解析为 SQL 可能需要几秒钟的时间。有没有人找到解决方法来获得预编译的查询?我真的不明白为什么会这么难;当然,参数很难处理,因为元素的数量可能不同,但有类似 SQL 就足够了

SELECT a, b, c from MyEntities
WHERE c in __PLACEHOLDER__

然后用实际的列表元素替换占位符。当然,它不如传递参数那么好,但它比等待几秒钟来一遍又一遍地解析整个表达式树要好得多。

【问题讨论】:

  • 列表不能用作参数,因此它会按照您的建议进行操作,即构造一个新查询,因为您的列表每次调用时都可以包含不同的元素。所以它实际上是一个 SQL 限制。
  • 不完全;如前所述,它每次都从头开始解析表达式树。我们有一个包含几个连接的查询,解析需要 5 秒(在 sql server 上需要几毫秒),这就是我正在寻找解决方法的原因。
  • @roland 这个列表有多大?你也必须使用包含吗?问题可能取决于每个条目的长度......他需要比较多少。在我使用 StringComparison.Ordinal 之前,我在 .StartsWith 上遇到了类似的问题,这大大加快了速度(与 .Contains 相比)。问题可能只是他需要遍历太大的字符串(太大需要时间)。如果您可以将其更改为 startswith 和 ordinal 它应该会大大加快(但取决于您的确切用例)。
  • 包含的问题只是.....sql 服务器是为这种类型的搜索而设计的,并针对它进行了优化,而 c# 无法在该领域使用它们。也许最好重组你的 sql 以在 sql 中搜索这些?
  • @Eldho:我对这篇文章很熟悉,我说的是表达式树的解析确实需要几秒钟的情况,即没有编译我们就会崩溃。

标签: c# entity-framework contains enumerable


【解决方案1】:

首先要了解“IN”运算符在参数化 SQL 查询中的工作原理。

SELECT A FOM B WHERE C IN @p 

不起作用,SQL 命令参数不接受 ARRAY 作为参数值,而是将查询转换为

SELECT A FROM B WHERE C IN (@p1, @p2, @p3 ... etc) 

此查询具有可变数量的参数,因此无法使用IEnumerable.Contains 预编译此查询。

唯一的其他选择(很长的路要走)是使用 Xml 或 Json(即将在 Sql 2016 中推出)。

将您的 IEnumerable 保存为 xml。

[10,20,20,50] can be translated to
<data>
   <int value="10"/>
   <int value="20"/>
   <int value="20"/>
   <int value="50"/>
</data>

然后你可以定义一个VIEW,参数为

选择 A 从 B WHERE C IN (SELECT INT FROM Xml(@P1))

您可以使用此视图,但在 EF 中如何触发此查询存在更多挑战,但此查询可以预编译,因为它只有一个参数。

用于性能黑客的自定义 SQL

对于非常简单的查询,例如,

List<Guid> ids;
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray();

我可以简单地使用自定义 SQL 并触发,

var parameterList = ids.Select( 
   (x,i)=> new SqlCommandParameter(
      "@p"+i, x));

var pnames = String.Join(",", parameterList.Select(x=> x.ParameterName));

var entities = 
    context.SqlQuery<MyEntity>(
       "SELECT * FROM TABLE WHERE Id in (" + pnames + ")",
        parameterList.ToArray());

临时表

您也可以使用临时表,但这会增加数据库中活动事务的数量。

Guid sid = Guid.NewGuid();
foreach(var p in ids){
    db.TempIDs.Add(new TempID{ SID = sid, Value = p });
}
db.SaveChanges();

var qIDs = db.TempIDs.Where( x=> x.SID == sid );

var myEntities db.MyEntities.Where( x => qIDs.Any( q.Value == x.Id) );

// delete all TempIDs...
db.SqlQuery("DELETE FROM TempIDs WHERE SID=@sid,
     new SqlCommandParameter("@sid", sid));

【讨论】:

  • 感谢您的回答;不幸的是,这仍然不是我想要的。如上所述,我们有很多这样的查询,编写大量自定义 SQL / 视图并不是真正可行的方法,因为这样我们将搞砸应用程序的可维护性。我想知道是否有任何方法可以向 EF 添加临时类型。想法:我们可以添加一个临时表来存储包含参数的列表,然后引入一个临时类型来制定查询。有谁知道 EF 是否允许引入临时类型?
  • 是的,除了 XML 或 JSON,您还可以使用临时表。但它会增加数据库的开销,会增加活动事务的数量,导致数据库引擎性能不佳。
  • 可以,但我可以将临时 C# 类型添加到 EF 吗?我需要它来制定查询,例如 context.MyEntities.Where(x => context.Set(typeof(TemporaryType)).Contains(x.Id))。我不介意在后台做所有需要的反射。
  • "... 无法使用 IEnumerable.Contains 预编译此查询" 当然,无法预编译为 SQL 命令。但是为什么 EF 不能以其他形式存储预编译的查询——例如,只引入一个专用的数据结构来存储预编译的查询?
猜你喜欢
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多