我正在衡量查询执行的差异,偶然发现了一个我没有解释的案例。查询应检索10000个客户的主要地址(客户可以有多个地址)。我们在导航属性中使用了两种不同的方法,这些方法在执行时间上有很大不同。
第一种方法以我通常编写Linq查询的方式检索客户:将结果直接写入业务对象并调用ToList()。此方法需要25秒才能执行。
第二种方法首先将客户检索为EF实体列表。 EF实体在foreach循环中转换为业务对象。此方法需要2秒钟才能执行。
有人可以解释一下这个区别吗?是否可以修改第一种方法,使执行时间与第二种方法类似?
private List<ICustomer> NavigationProperties_SO(int method)
{
using (Entities context = new Entities())
{
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
List<ICustomer> customerList = new List<ICustomer>();
if (method == 1)
{
// Execution time: 25 seconds
customerList = (from c in context.cust
.Include(o => o.AddressList)
.Include(o => o.AddressList.Select(p => p.ADDR))
let mainAddress = c.AddressList.Where(o => o.Main_addr == "1").FirstOrDefault()
select new Customer
{
cust = c,
mainAddress = mainAddress,
addr = mainAddress == null ? null : mainAddress.ADDR
}).AsNoTracking().ToList<ICustomer>();
}
else if (method == 2)
{
// Execution time: 2 seconds
var tempList = (from c in context.cust
.Include(o => o.AddressList)
.Include(o => o.AddressList.Select(p => p.ADDR))
select c).AsNoTracking().ToList();
foreach (var c in tempList)
{
ICustomer customer = new Customer();
var mainaddress = c.AddressList.Where(o => o.Main_addr == "1").FirstOrDefault();
customer.cust = c;
customer.mainAddress = mainaddress;
customer.addr = mainaddress == null ? null : mainaddress.ADDR;
customerList.Add(customer);
}
}
return customerList;
}
}
修改
以下是Entity Framework生成的(简化)查询:
方法1
SELECT
*
FROM [DBA].[CUST] AS [Extent1]
OUTER APPLY (SELECT TOP ( 1 )
*
FROM [DBA].[CUST_ADDR] AS [Extent2]
WHERE (([Extent1].[Id] = [Extent2].[Id]) AND (N'1' = [Extent2].[Main_addr])
ORDER BY 'a' ) AS [Limit1]
LEFT OUTER JOIN [DBA].[ADDR] AS [Extent3] ON [Limit1].[Id] = [Extent3].[Id]
方法2
SELECT
*
FROM ( SELECT
*
FROM [DBA].[CUST] AS [Extent1]
LEFT OUTER JOIN (SELECT *
FROM [DBA].[CUST_ADDR] AS [Extent2]
LEFT OUTER JOIN [DBA].[ADDR] AS [Extent3] ON [Extent2].[Id] = [Extent3].[Id] ) AS [Join1] ON ([Extent1].[Id] = [Join1].[Id])
) AS [Project1]
不同之处在于第一种方法在查询('class')中进行过滤,而第二种方法在循环中检索所有记录和过滤器。
答案 0 :(得分:1)
我怀疑
let mainAddress = c.AddressList.Where(o => o.Main_addr == "1").FirstOrDefault()
是罪魁祸首。某些查询强制EF要求返回所有可能的组合。 EF然后花费一点时间缩小范围,然后它为您提供合理的结果集。您可以使用SQL Server Profiler查看生成的查询。
在任何情况下,您都可以在第二种方法结束时使用LINQ而不是foreach(这不会有助于提高性能,但可读性可能会提高):
return tempList.Select(c => new Customer{cust=c, mainAddress = c.AddressList.FirstOrDefault(o=>o.Main_addr=="1"), ...);
答案 1 :(得分:1)
与评论相关的答案......(但有两条评论意见)
关于“如何选择最佳语法”部分
我会说它部分来自“经验”(参见,9Rune5和我怀疑同一点,在看到生成的sql之前这是有问题的):但是经验,有时候,也可能导致错误的结论;)
所以为了更务实,我建议你使用工具/库来帮助你通过查询或页面查看生成的sql / time ...
ANTS Performance Profiler,Miniprofiler,Sql Server Profiler等,它可能取决于您的技术/需求......
顺便说一句,如果您想保留“linq”语法,可以选择
var tempList = context.cust
.Include(o => o.AddressList)
.Include(o => o.AddressList.Select(p => p.ADDR))
.AsNoTracking()
.ToList();
var result = (from c in tempList
let mainAddress = c.AddressList.Where(o => o.Main_addr == "1").FirstOrDefault()
select new Customer
{
cust = c,
mainAddress = mainAddress,
addr = mainAddress == null ? null : mainAddress.ADDR
}).ToList<ICustomer>();
但并不比foreach语法更简洁......