6

我正在编写一段代码,由一位同事编写,它与我们公司使用的 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
                            });

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

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

4

2 回答 2

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,您将回退到客户端过滤。

于 2012-04-07T18:45:55.167 回答
0

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

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

于 2012-04-07T15:29:25.263 回答