在EntityFramework的ObjectSet中,为保持性能,我可以使用多少个Include?

50

我正在使用以下的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;

它具有一个很长的对象图并使用了许多Includes。它目前运行得非常完美,但是当网站有很多用户时,它会对性能产生很大影响吗?

我应该用其他方式吗?

5个回答

88

使用 includes 的查询会返回一个结果集,而包含的数量会影响从数据库服务器传输到 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.

这意味着每个订单都会重复列出Customers的数据。现在我们通过添加其他实体来扩展示例 - 'OrderLine (Id, OrderId, ProductId, Quantity)Product (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);

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

编辑:我刚刚在Data UserVoice上创建了提案,以支持额外的急切加载策略,在同一数据库查询中创建单独查询所产生的附加结果集来传递急切加载的数据。如果您觉得这个改进很有趣,请不要忘记为该提案投票。


1
+5(如果我能的话)我之前不知道这一点,并且相当天真地使用急切加载进行工作。这是一个非常好的例子,说明要有效地使用ORM,仍然需要对“ORM”中的“R”有相当的了解 - 不幸的是... - Slauma

15

通过像下面这样从数据库创建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 感谢您至少从这篇文章中受益。 - Sampath
1
奇怪的是,你是对的,它确实有效。在我的情况下,合并的单个查询无法纠正SQL,但是两个单独的查询可以工作。 - Ian

8
是的,如果在主表行上展开多个详细行,请避免使用Include。我相信EF将查询转换为一个大联接而不是几个查询。因此,您将在每个详细表的每一行上复制主表数据。例如:主->详细信息。假设主表有100行,详细信息有5000行(每个主表有50个)。如果您惰性加载详细信息,则返回100行(大小:主)+ 5000行(大小:详细信息)。如果您使用.Include(“Details”),则返回5000行(大小:主+详细信息)。基本上,主部分会重复50次。如果您包括多个表,则会向上乘以。检查由EF生成的SQL。

1
+1 我自己发现了这个。创建多个较小的查询并单独执行它们总是比一个一个地运行它们更好。但好的是,通过 EF 的魔力,它会自动为您构建对象图。因此,如果您在一个查询中加载用户,然后加载技能,它们将自动出现在彼此的导航属性中。(我假设这是 EF 的通用情况,因为我使用 Code First)。 - djdd87
@泛型类型Tea,我认为它是EF的通用类型。事实上,我认为他们在第一次访问时构建导航属性... - Stephen Chung

3

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

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

Customer (Id、Name、Address)

它映射到这个表:

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    

3

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接