2013-03-21 131 views
2

我有一个报告,显示了一个确定的商家订单,并且它工作正常,直到我需要添加一个过滤器的付款状态。优化LINQ查询

这是我如何通过过滤器生成查询,过滤器:

var queryOrder = context.Orders.Select(m=>m); 

if (viewModel.InitialDate.HasValue) 
    queryOrder = queryOrder.Where(m => m.CreatedDate.Date >= viewModel.InitialDate.Value); 

(...) /* continues building the query, filter by filter */ 


if (viewModel.SelectedPaymentStatus != null) 
    queryOrder = queryOrder.Where(m => viewModel.SelectedPaymentStatus.Contains(m.Payments.Select(p => p.PaymentStatusId).Single().ToString())); 

queryOrder = queryOrder.Where(m => m.MerchantId == merchantId); 

当我运行queryOrder,哪怕它只是一个queryOrder.Count(),它需要1分钟来执行。使用SQL Server的性能分析工具,我提取生成的查询是这样的:

SELECT [t0].[Id], [t0].[CustomerId], [t0].[MerchantId], [t0].[OrderNumber], [t0].[Amount], [t0].[SoftDescriptor], [t0].[ShippingMethod], [t0].[ShippingPrice], [t0].[IpAddress], [t0].[SellerComment], [t0].[CreatedDate] 
FROM [dbo].[Order] AS [t0] 
WHERE ([t0].[MerchantId] = @p0) 
AND ((CONVERT(NVarChar,(
     SELECT [t1].[PaymentStatusId] 
     FROM [dbo].[Payment] AS [t1] 
     WHERE [t1].[OrderId] = [t0].[Id] 
    ))) IN (@p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8)) 

的@ P0参数是MERCHANTID一个GUID和@ P1直通@ P8是数字串"1""8",代表paymentStatusId的。

如果我跳过行:

if (viewModel.SelectedPaymentStatus != null) 
    queryOrder = queryOrder.Where(m => viewModel.SelectedPaymentStatus.Contains(m.Payments.Select(p => p.PaymentStatusId).Single().ToString())); 

查询运行在不到1秒钟。但是当我使用它时,表演击中了地板。有关如何解决此问题的任何提示?

+0

你说的生成的SQL运行速度更快? – 2013-03-21 14:50:34

+0

和这个实体框架或LINQ到SQL? – 2013-03-21 14:52:09

+0

不,即使在sql管理工作室中,生成的sql运行速度也很慢。这是LINQ to sql – leobelones 2013-03-21 14:54:59

回答

0

所有的查询都被延迟,这既是linq的好/坏部分。尝试拆分查询并使用一些内存中的结果。 尝试删除第一个查询(实际上没什么意义,您将返回相同的集合),并将第二个查询修改为这样,并查看它是否有任何区别。

var clause = context.Orders.Payments.Select(p => p.PaymentStatusId).Single().ToString(); 
if (viewModel.SelectedPaymentStatus != null) 
    var queryOrder = context.Orders.queryOrder.Where(m => viewModel.SelectedPaymentStatus.Contains(clause)); 
+0

第一个查询并不像你想象的那么糟糕。身份选择并不会真的引起任何问题(尤其是在查询提供程序将基本意识到这是一个无操作的问题)。他真正在做的是确保'queryOrder'类型为'IQueryable',而不是底层的集合类型,这可以通过调用'AsQueryable'来得到更好的服务,它可以做同样的事情,但是可以更好地表达意义;另一种选择是不使用'var',而是手动输出变量成为'IQueryable'。 – Servy 2013-03-21 15:22:23

+0

没错,只是因为DbSet已经实现了IQueryable,所以它看起来是多余的。顺便说一下,我认为不同之处在于删除Contains的子句并将其作为内存中的结果。 – 2013-03-21 15:50:56

0

我注释的问题摘录:

if (viewModel.SelectedPaymentStatus != null) { 

    // Give me only orders from queryOrder where... 
    queryOrder = queryOrder.Where(

     // ...my viewModel's SelectedPaymentStatus collection... 
     m => viewModel.SelectedPaymentStatus.Contains(

       // ...contains the order's payment's PaymentStatusId... 
       m.Payments.Select(p => p.PaymentStatusId).Single() 

              // ... represented as a string?! 
             .ToString() 
              // Why are database IDs strings? 
      ) 
     ); 
} 

viewModel.SelectedPaymentStatus似乎是字符串的集合;因此,您要求数据库将PaymentStatusId转换为nvarchar,并使用SelectedPaymentStatus的元素进行字符串比较。呸。

由于viewModel.SelectedPaymentStatus小,它可能是最好创建一个临时List<int>和使用,在您的查询:

if (viewModel.SelectedPaymentStatus != null) { 

    // Let's do the conversion once, in C# 
    List<int> statusIds = viewModel.SelectedPaymentStatus.Select(i => Convert.ToInt32(i)).ToList(); 

    // Now select the matching orders 
    queryOrder = queryOrder.Where(
        m => statusIds.Contains(
           m.Payments.Select(p => p.PaymentStatusId).Single()) 
         ) 
       ); 
}