EF在Linq或foreach

时间:2015-07-16 11:53:54

标签: c# linq entity-framework

我正在衡量查询执行的差异,偶然发现了一个我没有解释的案例。查询应检索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')中进行过滤,而第二种方法在循环中检索所有记录和过滤器。

2 个答案:

答案 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语法更简洁......