【问题标题】:LINQ to Entities query takes long to compile, SQL runs fastLINQ to Entities 查询编译时间长,SQL 运行速度快
【发布时间】:2012-04-07 13:21:20
【问题描述】:

我正在编写一段代码,由一位同事编写,它与我们公司使用的 CRM 应用程序交互。这段代码中有两个 LINQ to Entities 查询在我们的应用程序中执行了很多次,我被要求优化它们,因为其中一个非常慢。

这些是查询:

第一个查询,这个查询几乎可以立即编译。它从 CRM 数据库中获取关系信息,通过应用程序给出的关系 ID 列表进行过滤:

from relation in context.ADRELATION
where ((relationIds.Contains(relation.FIDADRELATION)) && (relation.FLDELETED != -1))
join addressTable in context.ADDRESS on relation.FIDADDRESS equals addressTable.FIDADDRESS
   into temporaryAddressTable
from address in temporaryAddressTable.DefaultIfEmpty()
join mailAddressTable in context.ADDRESS on relation.FIDMAILADDRESS equals
   mailAddressTable.FIDADDRESS into temporaryMailAddressTable
from mailAddress in temporaryMailAddressTable.DefaultIfEmpty()
select new { Relation = relation, Address = address, MailAddress = mailAddress };

第二个查询,大约需要 4-5 秒来编译,并从数据库中获取有关人员的信息(再次通过 ID 列表过滤):

from role in context.ROLE
join relationTable in context.ADRELATION on role.FIDADRELATION equals relationTable.FIDADRELATION into temporaryRelationTable
from relation in temporaryRelationTable.DefaultIfEmpty()
join personTable in context.PERSON on role.FIDPERS equals personTable.FIDPERS into temporaryPersonTable
from person in temporaryPersonTable.DefaultIfEmpty()
join nationalityTable in context.TBNATION on person.FIDTBNATION equals nationalityTable.FIDTBNATION into temporaryNationalities
from nationality in temporaryNationalities.DefaultIfEmpty()
join titelTable in context.TBTITLE on person.FIDTBTITLE equals titelTable.FIDTBTITLE into temporaryTitles
from title in temporaryTitles.DefaultIfEmpty()
join suffixTable in context.TBSUFFIX on person.FIDTBSUFFIX equals suffixTable.FIDTBSUFFIX into temporarySuffixes
from suffix in temporarySuffixes.DefaultIfEmpty()
where ((rolIds.Contains(role.FIDROLE)) && (relation.FLDELETED != -1))
select new { Role = role, Person = person, relation = relation, Nationality = nationality, Title = title.FTXTBTITLE, Suffix = suffix.FTXTBSUFFIX };

我已设置 SQL Profiler 并从两个查询中获取 SQL,然后在 SQL Server Management Studio 中运行它。即使有大量(~1000)个 ID,这两个查询都运行得非常快。所以问题似乎出在 LINQ 查询的编译上。

我尝试使用已编译的查询,但由于那些只能包含原始参数,我不得不用过滤器去除部分并在调用 Invoke() 后应用它,所以我不确定这是否有帮助很多。此外,由于此代码在 WCF 服务操作中运行,我不确定编译后的查询是否还会在后续调用中存在。

最后我尝试的是在第二个查询中只选择一列。虽然这显然不会给我所需的信息,但我认为它会比我们现在选择的约 200 列更快。没有这样的情况,仍然需要4-5秒。

我根本不是 LINQ 专家,所以我几乎无法理解这段代码(我觉得它写得不是最理想的,但我不能指望它)。谁能告诉我为什么会出现这个问题?

我剩下的唯一解决方案是手动选择所有信息,而不是加入所有这些表。然后我会得到大约 5-6 个查询。我想还不错,但由于我没有在这里处理极其低效的 SQL(或者至少是可接受的低效率水平),我希望能防止这种情况发生。

提前谢谢,希望我说清楚了。如果没有,请随时询问,我会提供更多详细信息。


编辑: 我最终在我的实体框架上添加了关联(目标数据库没有指定外键)并因此重写了查询:

context.ROLE.Where(role => rolIds.Contains(role.FIDROLE) && role.Relation.FLDELETED != -1)
            .Select(role => new 
                            { 
                                ContactId = role.FIDROLE, 
                                Person = role.Person, 
                                Nationality = role.Person.Nationality.FTXTBNATION,
                                Title = role.Person.Title.FTXTBTITLE,
                                Suffix = role.Person.Suffix.FTXTBSUFFIX
                            });

似乎更具可读性,而且速度也更快。

感谢您的建议,我一定会牢记关于针对不同数量的参数进行多个编译查询的建议!

【问题讨论】:

    标签: c# linq-to-entities sql-server-2008-r2


    【解决方案1】:

    Gabriels 的答案是正确的:使用已编译的查询。

    看起来您正在为每个 WCF 请求重新编译它,这当然违背了一次性初始化的目的。而是将编译后的查询放入静态字段。

    编辑:

    执行此操作:向您的服务发送最大负载并暂停调试器 10 次。查看调用堆栈。它是在 L2S 代码还是在 ADO.NET 代码中更频繁地停止?这将告诉您问题仍然存在于 L2S 还是 SQL Server。

    接下来,让我们修复过滤器。我们需要将它推回编译后的查询中。这只有通过转换才能实现:

    rolIds.Contains(role.FIDROLE)
    

    到这里:

    role.FIDROLE == rolIds_0 || role.FIDROLE == rolIds_1 || ...
    

    您需要为每个 rolIds 基数创建一个新的编译查询。这很讨厌,但有必要让它编译。在我的项目中,我已将这项任务自动化,但您可以在此处执行一次性解决方案。

    我猜大多数查询将只有很少的角色 ID,因此您可以针对基数 1-10 实现 10 个已编译查询,如果基数超过 10,您将回退到客户端过滤。

    【讨论】:

    • 正如我在 Gabriel GM 的回答中所说,我确实将它们放在了一个静态字段中。不过,我想我会再试一次,以确保 110% 的肯定。但这会起作用吗,因为我只能将部分放在已编译查询中的 where 子句中?
    • 哦,关于基数的好主意!没错,大多数请求的角色 ID 都少于 10 个。绝对值得一看。谢谢!
    【解决方案2】:

    如果您决定将查询保留在代码中,则可以编译它。当您运行您的应用程序时,您仍然必须编译一次查询,但所有后续调用都将使用该已编译的查询。您可以在此处查看 MSDN 帮助:http://msdn.microsoft.com/en-us/library/bb399335.aspx

    另一种选择是使用存储过程并从您的代码中调用该过程。因此没有编译时间。

    【讨论】:

    • 我尝试使用编译查询,但每次调用服务操作仍然需要4秒。我有一种感觉,它没有被保留在请​​求之间(我确实使它成为静态的)。我也不能使用存储过程,因为如果我们接触他们的数据库而不是从数据库中读取,制造 CRM 软件包的公司将停止提供支持:/ 如果我重写查询以使用导航属性而不是连接可能会有所帮助吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多