【问题标题】:Faster ways to access subsets through LINQ通过 LINQ 访问子集的更快方法
【发布时间】:2012-09-06 14:17:38
【问题描述】:

这是一个关于 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();

这非常慢并且需要很长时间才能运行,当我对其运行集成测试时,测试似乎超时。我想知道是否有更快的方法来做到这一点。

【问题讨论】:

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


    【解决方案1】:

    尝试在每次查询后致电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));
    

    【讨论】:

    • 如果后面有 IQueryable 则尤其如此。
    • 有没有办法更快地计数?另外,当我对 var 的内容进行预览时,它会超时。
    • @tehdoommarine 我不明白你的意思。只需调用 Count() 即可获取查询返回的元素数量。
    • 啊,没关系。完美运行。正在对列表进行计数,这是缓慢而艰苦的
    【解决方案2】:

    嗯,一方面,将这些调用结合起来会加快一些速度。试试这个:

    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();
    

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

    【讨论】:

    • 波什。现在这可以在数据库中运行,而不是将所有这些行带到客户端。现在任何低效率都可能归因于缺少索引。
    • 不需要需要将所有内容都放在一个语句中。只是不要在每条语句之后调用ToList,所有内容都将作为数据库中的一条语句运行。
    • 10 LINQ Myths的神话8
    • @david.s 好文章。我知道这一点,但是(如果我在这里错了,请纠正我)我相信我在将它们放入一个语句时引入的操作顺序的改变应该会固有地加快速度。不过你是对的——我认为这是真正的组合产生了影响。
    • @spender 我对这里使用bosh 有点困惑,哈哈
    【解决方案3】:

    类似于@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))});
    

    【讨论】:

      【解决方案4】:

      我知道您有一个可接受的答案,但请尝试这个以提高速度 - PLINQ(并行 LINQ)这将获取 4000 个列表,如果您有 4 个内核,它将在每个内核上过滤 1000 个,然后整理结果。

        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

      【讨论】:

      • 嗯。如果有大量已处理的订单并且订单是来自数据库的 IQueryable,那么这将非常低效,从数据库中提取一大堆已处理的项目。将所有内容保存在数据库中将使数据库能够自行决定工作负载是否需要并行化。还值得记住的是,除非显式加载,否则访问导航属性(例如 ProductsInOrder)将导致另一个数据库命中。 ... 对于每个订单。
      • 我只是认为这是来自 LINQ 的整体,对象并没有意识到它使用的是 SQL 数据库或实体框架。我想知道您可以使用对象加速普通 LINQ 总是很好,即使这是来自实体框架的存储库?
      • 当然。 OP 没有明确数据源,但我认为这是来自数据库,因为我看到“我们将拥有三个表”的语句
      【解决方案5】:

      将其移至 1 个查询中,但实际上您应该将其移至 SSIS 包或 sql 作业中。你可以很容易地把它变成一个在不到一秒的时间内运行的存储过程。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-22
        • 1970-01-01
        • 1970-01-01
        • 2020-12-14
        相关资源
        最近更新 更多