通过LINQ访问子集的更快方法

时间:2012-09-06 14:17:38

标签: c# asp.net sql asp.net-mvc linq

这是一个关于SPEED的问题 - 有很多记录需要访问。

有关问题的基本信息

例如,我们将在数据库中有三个表。

关系: Order-ProductInOrder是一对多(订单可以包含许多产品) ProductInOrder-产品是一对一的(订单中的产品由一种产品代表)

public class Order {
  public bool Processed { get; set; }
  // this determines whether the order has been processed
  // - orders that have do not go through this again
  public int OrderID { get; set; } //PK
  public decimal TotalCost{ get; set; }
  public List<ProductInOrder> ProductsInOrder;
  // from one-to-many relationship with ProductInOrder
  // the rest is irrelevant and will not be included here
}
//represents an product in an order - an order can have many products
public class ProductInOrder {
  public int PIOD { get; set; } //PK
  public int Quantity{ get; set; }
  public int OrderID { get; set; }//FK
  public Order TheOrder { get; set; }
  // from one-to-many relationship with Order
  public int ProductID { get; set; } //FK
  public Product TheProduct{ get; set; }
  //from one-to-one relationship with Product
}
//information about a product goes here
public class Product {
  public int ProductID { get; set; } //PK
  public decimal UnitPrice { get; set; } //the cost per item
  // the rest is irrelevant to this question
}

假设我们收到一批订单,我们需要在这些订单中应用折扣并查找订单的总价。这适用于10,000到100,000多个订单。这种方式的工作方式是,如果订单有5个或更多的产品,其中每个产品的成本是100美元,我们将对总价格给予10%的折扣。

我尝试过什么

我尝试了以下内容:

//this part gets the product in order with over 5 items
List<Order> discountedOrders = orderRepo
  .Where(p => p.Processed == false)
  .ToList();
List<ProductInOrder> discountedProducts = discountedOrders
  .SelectMany(p => p.ProductsInOrder)
  .Where(q => q.Quantity >=5 )
  .ToList();
discountedProducts = discountedProducts
  .Where(p => p.Product.UnitPrice >= 100.00)
  .ToList();
discountOrders = discountedOrders
  .Where(p => discountProducts.Any(q => q.OrderID == p.OrderID))
  .ToList();

这非常慢并且需要永远运行,当我对它运行集成测试时,测试似乎超时了。我想知道是否有更快的方法来做到这一点。

5 个答案:

答案 0 :(得分:2)

尝试在每次查询后调用ToList

当您在查询上调用ToList时,它将被执行并且对象将从内存中的数据库加载。基于第一个查询的结果的任何后续查询都在列表的内存中执行,而不是直接在数据库中执行。你想要做的是在数据库上执行整个查询,只返回那些验证你所有条件的结果。

var discountedOrders = orderRepo
  .Where(p=>p.Processed == false);
var discountedProducts = discountedOrders
  .SelectMany(p=>p.ProductsInOrder)
  .Where(q=>q.Quantity >=5);
discountedProducts = discountedProducts
  .Where(p=>p.Product.UnitPrice >= 100.00);
discountOrders = discountedOrders
  .Where(p=>discountProducts.Any(q=>q.OrderID == p.OrderID));

答案 1 :(得分:1)

嗯,首先,结合这些调用会加快一些速度。试试这个:

discountOrders =  orderRepo.Where(p=>p.Processed == false && p.SelectMany(q=>q.ProductsInOrder).Where(r=>r.Quantity >=5 && r.Product.UnitPrice >= 100.00 && r.OrderID == p.OrderId).Count() > 0).ToList();

请注意,这未经过测试。我希望我的逻辑正确 - 我想我做到了,但如果我没有,请告诉我。

答案 2 :(得分:1)

与@PhillipSchmidt相似,你可以合理化你的Linq

var discountEligibleOrders =
 allOrders
   .Where(order => !order.Processed 
                   && order
                    .ProductsInOrder
                    .Any(pio => pio.TheProduct.UnitPrice >= 100M 
                                && pio.Quantity >= 5))

删除所有那些讨厌的ToList语句是一个很好的开始,因为你可能会将数据库中可能显着更大的集合提升到你的应用程序。让数据库完成工作。

获取每个订单及其价格(假设折扣价格为0.9 *上市价格):

var ordersAndPrices =
 allOrders
   .Where(order => !order.Processed)
   .Select(order => new {
                     order, 
                     isDiscounted = order
                       .ProductsInOrder
                       .Any(pio => pio.TheProduct.UnitPrice >= 100M 
                                   && pio.Quantity >= 5)
                    })
   .Select(x => new {
                  order = x.order, 
                  price = x.order
                           .ProductsInOrder
                           .Sum(p=> p.Quantity 
                                    * p.TheProduct.UnitPrice
                                    * (x.isDiscounted ? 0.9M : 1M))});

答案 3 :(得分:0)

我知道你有一个已接受的答案,但请尝试这个以增加速度 - PLINQ(并行LINQ)这将采用4000列表,如果你有4个核心,它将在每个核心上过滤1000,然后整理结果。 / p>

  List<Order> orders = new List<Order>();
  var parallelQuery = (from o in orders.AsParallel()
                       where !o.Processed
                       select o.ProductsInOrder.Where(x => x.Quantity >= 5 &&
                                                           x.TheProduct.UnitPrice >= 100.00 && 
                                                           orders.Any(x => x.OrderID = x.OrderID));

请看这里:

在许多情况下,通过更有效地使用主机上的所有可用内核,PLINQ可以显着提高LINQ to Objects查询的速度。这种提高的性能为桌面带来了高性能计算能力

http://msdn.microsoft.com/en-us/library/dd460688.aspx

答案 4 :(得分:-1)

将其移动到1个查询中,但实际上您应该将其移动到SSIS包或sql作业中。您可以轻松地将其设置为在不到一秒钟内运行的存储过程。