【问题标题】:Understanding how LINQ compiles to ADO.NET SQL statements了解 LINQ 如何编译为 ADO.NET SQL 语句
【发布时间】:2018-04-01 23:57:25
【问题描述】:

我有一个看起来非常简单的 LINQ 语句,但它生成的 SQL 对我来说没有意义,而且它的执行时间比我预期的要长得多。我只是想了解 LINQ 在做什么,以便弄清楚为什么它运行得这么慢。一条类似的 SQL 语句需要不到一秒的时间,但 LINQ 大约需要 20 秒。

代码如下:

// This line takes 20 seconds to return.
var alertEvents = GetFilteredAlertEvents(alert.AlertEvents, db).ToList<AlertEvent>();

private static IEnumerable<AlertEvent> GetFilteredAlertEvents(ICollection<AlertEvent> alertEvents, SlalomEREntities db)
{
    Guid marketAlertReceiverGroupId = new Guid(ConfigurationManager.AppSettings["MarketAlertReceiverGroupId"]);
    var subQuery = from ae in alertEvents
                   join tr in db.TargetResources on ae.ResourceId equals tr.ResourceID
                   join atr in db.AlertTargetResources on tr.ResourceID equals atr.TargetResourceID
                   where atr.AlertTargetID == marketAlertReceiverGroupId
                   select ae.AlertEventId;

     return from alertEvent in alertEvents
            where !subQuery.Contains(alertEvent.AlertEventId)
            select alertEvent;
}

在 SSMS 中,子选择返回 3126 行没有where,只有 127 行有它。 SSMS 中的主要 select 返回 365 行,没有子选择。带有子选择的完整选择返回 238 行。没有很多数据。

使用 Visual Studio 的诊断工具,我看到 LINQ 生成了 14 条 SQL 语句。它们中的每一个都是一个简单的 SQL 选择,我没有看到任何连接,也没有看到 where 比较。这是我在诊断工具事件窗口中看到的示例 SQL 语句:

SELECT 
[Extent1].[AlertTargetID] AS [AlertTargetID], 
[Extent1].[TargetResourceID] AS [TargetResourceID], 
[Extent1].[CreatedAt] AS [CreatedAt], 
[Extent1].[CreatedBy] AS [CreatedBy]
FROM [dbo].[AlertTargetResource] AS [Extent1]

还有 13 条类似的 SQL 语句。

这是我要复制的 SQL。

select *
from AlertEvent ae1
where ae1.AlertEventId not in
(select ae.AlertEventId
from AlertEvent ae 
join TargetResource tr on ae.ResourceId = tr.ResourceID
join AlertTargetResource atr on atr.TargetResourceID = tr.ResourceID
where atr.AlertTargetID = '89bd4ea5-5d56-4b8a-81ba-5a9e5991ba64')

这是我的问题:

  1. 为什么 LINQ 对 14 个简单的选择而不是带有连接和位置的单个选择进行分类?
  2. 如何加快我认为很简单的代码?

【问题讨论】:

  • 这可能是因为您在 .dbml 文件中将更新检查属性设置为始终。如果它是一个缓慢的查询,您可能需要将其删除或使用数据库视图以便将其缓存。
  • 是的,这不是一个好方法。您需要组合这两个查询,因为第二个查询(return 语句中的那个)看起来必须执行多个查询。要么,要么使用ToList()subquery 转换为数据。
  • alert.AlertEvents也是数据库表,不是内存列表?

标签: c# performance tsql linq-to-sql


【解决方案1】:

在您方法的第一部分,subQuery 是一个尚未针对数据库运行的查询。在第二部分(围绕您的 return 语句),您将多次调用该查询。

Entity Framework 将如何处理这样的情况并不总是很明显,但这里它似乎为alertEvents 中的每个项目调用查询。

您真正想要的是查询返回的 ID 列表,然后将该列表用于方法的第二部分。要将查询转换为该查询返回的数据,您可以使用ToList()。该扩展方法将执行查询并返回数据结果。

在下面的代码中,subQuery 现在是一个 ID 集合。

private static IEnumerable<AlertEvent> GetFilteredAlertEvents(ICollection<AlertEvent> alertEvents, SlalomEREntities db)
{
    Guid marketAlertReceiverGroupId = new Guid(ConfigurationManager.AppSettings["MarketAlertReceiverGroupId"]);
    var subQuery = (from ae in alertEvents
                    join tr in db.TargetResources on ae.ResourceId equals tr.ResourceID
                    join atr in db.AlertTargetResources on tr.ResourceID equals atr.TargetResourceID
                    where atr.AlertTargetID == marketAlertReceiverGroupId
                    select ae.AlertEventId).ToList();

     return from alertEvent in alertEvents
            where !subQuery.Contains(alertEvent.AlertEventId)
            select alertEvent;
}

【讨论】:

  • 这产生了巨大的变化。我喜欢漂亮、简单的解决方案。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-12
  • 2016-03-29
相关资源
最近更新 更多