【问题标题】:How many Include I can use on ObjectSet in EntityFramework to retain performance?我可以在 EntityFramework 的 ObjectSet 上使用多少个 Include 来保持性能?
【发布时间】:2011-07-28 03:55:28
【问题描述】:

我正在为我的个人资料页面使用以下 LINQ 查询:

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .Include("UserInterests.Interest")
                        .Include("UserMessengers.Messenger")
                        .Include("UserFriends.User.UserSkills.Skill")
                        .Include("UserFriends1.User1.UserSkills.Skill")
                        .Include("UserFriends.User.UserIdeas")
                        .Include("UserFriends1.User1.UserIdeas")
                               where u.UserId == userId
                               select u;

它有一个很长的对象图并使用许多包含。它现在运行得很完美,但是当网站有很多用户时,它会对性能产生很大影响吗?

我应该用其他方式吗?

【问题讨论】:

    标签: performance linq entity-framework asp.net-mvc-2 linq-to-entities


    【解决方案1】:

    包含包含的查询返回单个结果集,包含的数量会影响大数据集从数据库服务器传输到 Web 服务器的方式。示例:

    假设我们有一个实体Customer (Id, Name, Address) 和一个实体Order (Id, CustomerId, Date)。现在我们要查询一位客户的订单:

    var customer = context.Customers
                          .Include("Orders")
                          .SingleOrDefault(c => c.Id == 1);
    

    生成的数据集将具有以下结构:

     Id | Name | Address | OrderId | CustomerId | Date 
    ---------------------------------------------------
      1 |  A   |   XYZ   |    1    |     1      | 1.1.
      1 |  A   |   XYZ   |    2    |     1      | 2.1.
    

    这意味着对于每个Order 重复Cutomers 数据。现在让我们用另一个实体扩展该示例 - 'OrderLine (Id, OrderId, ProductId, Quantity)andProduct (Id, Name)'。现在我们要查询一位客户的订单、订单行和产品:

    var customer = context.Customers
                          .Include("Orders.OrderLines.Product")
                          .SingleOrDefault(c => c.Id == 1);
    

    生成的数据集将具有以下结构:

     Id | Name | Address | OrderId | CustomerId | Date | OrderLineId | LOrderId | LProductId | Quantity | ProductId | ProductName
    ------------------------------------------------------------------------------------------------------------------------------
      1 |  A   |   XYZ   |    1    |     1      | 1.1. |     1       |    1     |     1      |    5     |    1      |     AA
      1 |  A   |   XYZ   |    1    |     1      | 1.1. |     2       |    1     |     2      |    2     |    2      |     BB
      1 |  A   |   XYZ   |    2    |     1      | 2.1. |     3       |    2     |     1      |    4     |    1      |     AA
      1 |  A   |   XYZ   |    2    |     1      | 2.1. |     4       |    2     |     3      |    6     |    3      |     CC
    

    如您所见,数据变得非常重复。一般来说,每个包含到参考导航属性(示例中为Product)将添加新列,并且每个包含到集合导航属性(示例中为OrdersOrderLines)将添加新列并复制已创建的行包含集合中的每一行。

    这意味着您的示例可以轻松拥有数百列和数千行,这是要传输的大量数据。正确的做法是创建性能测试,如果结果不能满足您的期望,您可以通过自己的查询或LoadProperty 方法分别修改查询和加载导航属性。

    单独查询示例:

    var customer = context.Customers
                          .Include("Orders")
                          .SingleOrDefault(c => c.Id == 1);
    var orderLines = context.OrderLines
                            .Include("Product")
                            .Where(l => l.Order.Customer.Id == 1)
                            .ToList();
    

    LoadProperty 的示例:

    var customer = context.Customers
                          .SingleOrDefault(c => c.Id == 1);
    context.LoadProperty(customer, c => c.Orders);
    

    此外,您应该始终只加载您真正需要的数据。

    编辑:我刚刚创建了proposal on Data UserVoice 以支持额外的预加载策略,其中预加载的数据将在额外的结果集中传递(由同一数据库往返中的单独查询创建)。如果您觉得这项改进很有趣,请不要忘记为该提案投票。

    【讨论】:

    • +5(如果可以的话)我没有意识到这一点,并且曾经很天真地使用急切加载。这是一个非常好的例子,要有效地使用 ORM,仍然需要多少关于“ORM”中“R”的知识 - 不幸的是......
    【解决方案2】:

    (您可以通过从数据库创建 2 个或更多小数据请求来提高许多包含的性能,如下所示。

    根据我的经验,只能每个查询最多包含 2 个包含,如下所示。超过此值会导致性能非常差。

    var userData = from u in db.Users
                            .Include("UserSkills.Skill")
                            .Include("UserIdeas.IdeaThings")
                            .FirstOrDefault();
    
     userData = from u in db.Users
                        .Include("UserFriends.User.UserSkills.Skill")
                        .Include("UserFriends1.User1.UserSkills.Skill")
                        .FirstOrDefault();
    

    以上将通过使用更多的行程将小型数据集从数据库中带入数据库。

    【讨论】:

    • @MikeCole 谢谢你至少从这篇文章中受益。
    • 奇怪的是,你是对的,它确实有效。在我的情况下,组合的单个查询无法更正 sql,但两个单独的查询可以工作。
    【解决方案3】:

    是的,它会的。如果它在主表行上展开多个详细信息行,请避免使用 Include。

    我相信 EF 会将查询转换为一个大型连接,而不是多个查询。因此,您最终会在明细表的每一行上复制主表数据。

    例如:主 -> 详细信息。比如说,master 有 100 行,Details 有 5000 行(每个 master 有 50 行)。

    如果您延迟加载详细信息,则返回 100 行(大小:主)+ 5000 行(大小:详细信息)。

    如果使用 .Include("Details"),则返回 5000 行(大小:master + details)。基本上,主部分被复制了 50 多次。

    如果你包含多个表,它会向上乘。

    检查EF生成的SQL。

    【讨论】:

    • +1 我自己发现了这个。创建多个较小的查询并分别执行它们总是比一个一个地运行它们更好。但好处是,借助 EF 的魔力,它会自动为您构建对象图。因此,如果您在一个查询中加载用户,然后加载您的技能,他们将自动出现在彼此的导航属性中。 (我假设这通常是 EF,因为我使用 Code First)。
    • @Generic Type Tea,我相信它对 EF 来说是通用的。事实上,我认为他们在首次访问时构建导航属性......
    【解决方案4】:

    我建议您执行负载测试并衡量网站在压力下的性能。如果您对每个请求执行复杂的查询,您可以考虑缓存一些结果。

    【讨论】:

      【解决方案5】:

      include的结果可能会改变:它取决于调用include方法的实体。

      就像 Ladislav Mrnka 提出的例子,假设我们有一个实体

      客户(ID、姓名、地址)

      映射到这张表:

      Id  |  Name   | Address
      -----------------------
      C1  |  Paul   |   XYZ   
      

      和一个实体订单(Id、CustomerId、Total)

      映射到这张表:

      Id |  CustomerId  | Total
      -----------------------
      O1 |      C1      |  10.00
      O2 |      C1      |  13.00
      

      关系是一个客户许多订单


      示例 1:客户 => 订单

      var customer = context.Customers
                            .Include("Orders")
                            .SingleOrDefault(c => c.Id == "C1");
      

      Linq 将被翻译成一个非常复杂的 sql 查询。

      在这种情况下,查询将产生两条记录,并复制有关客户的信息。

       Customer.Id   |   Customer.Name |    Order.Id |  Order.Total
      -----------------------------------------------------------
           C1        |       Paul      |       O1    |    10.00     
           C1        |       Paul      |       O2    |    13.00   
      

      示例 2:订单 => 客户

      var order = context.Orders
                            .Include("Customers")
                            .SingleOrDefault(c => c.Id == "O1");
      

      Linq 会被翻译成一个简单的 sql Join。

      在这种情况下,查询将只产生一条记录,没有重复信息:

       Order.Id |  Order.Total |  Customer.Id   |   Customer.Name
      -----------------------------------------------------------
           O1   |    10.00     |      C1        |       Paul    
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-18
        • 2014-06-10
        • 2023-03-03
        • 1970-01-01
        • 2012-08-27
        相关资源
        最近更新 更多