在EFCore 中,数据加载策略对于提高应用程序的性能至关重要,数据加载是指从数据库中检索实体及其相关的导航属性。EF Core 提供了以下几种数据加载方式,本篇文章将详细介绍 EF Core中的数据加载。
1.延迟加载(Lazy Loading)
官网说明:https://learn.microsoft.com/zh-cn/ef/core/querying/related-data/lazy
延迟加载是 EF Core 默认的数据加载方式。当访问一个实体中的导航属性时,EF Core 会自动从数据库中加载相关的实体。例如,我们可以通过以下代码获取一个订单的所有订单项:
var order = dbContext.Orders.Find(orderId);
var orderItems = order.OrderItems; // 自动加载所有订单项
在上面的代码中,我们首先从数据库中获取了一个订单实体,然后通过订单实体的导航属性 OrderItems 获取了该订单的所有订单项。EF Core 会自动发出另一次查询语句,从数据库中获取所有订单项。这种加载方式称为延迟加载,因为 EF Core 延迟加载了订单项,直到我们访问了 OrderItems 属性时才加载。
延迟加载的优点是方便快捷,不需要手动编写额外的代码来加载关联实体。但是,也存在一些缺点。由于 EF Core 需要发出多条查询语句,延迟加载可能会导致性能问题。此外,如果在离开 DbContext 的范围之后访问导航属性,将会引发异常。
2.显式加载(Explicit Loading)
官网说明:https://learn.microsoft.com/zh-cn/ef/core/querying/related-data/explicit
显式加载是另外一种数据加载方式,它要求我们手动加载关联实体。显式加载通常用于需要精细控制数据加载和提高性能的场景。
在 EF Core 中,可以使用 Load 方法进行显式加载。例如,我们可以通过以下代码手动加载一个订单的所有订单项:
var order = dbContext.Orders.Find(orderId);
dbContext.Entry(order).Collection(o => o.OrderItems).Load(); // 手动加载所有订单项
在上面的代码中,我们首先从数据库中获取了一个订单实体,然后使用 Entry 方法获取与该实体相关联的 DbContext 中的实体条目,最后调用 Collection 方法指定要加载的导航属性,并使用 Load 方法手动加载所有订单项。这种加载方式称为显式加载,因为我们需要显式地编写代码来加载关联实体。
显式加载的优点是可以减少不必要的查询,提高性能。此外,它还允许我们精细控制数据加载,避免在加载大量数据时出现性能问题。但是,它需要我们手动编写额外的代码来加载关联实体。
3.预先加载 (Eager Loading)
官网说明:https://learn.microsoft.com/zh-cn/ef/core/querying/related-data/eager
在查询时,通过使用 Include 方法指定要加载的导航属性,从而一次性加载所有相关的实体。这种加载方式称为预先加载(Eager Loading)。预先加载允许我们在单个查询中检索所有实体和导航属性,避免了通过多次查询检索数据的开销,提高了性能。
当使用 EF Core 进行预先加载(Eager Loading)时,可以通过使用 Include 方法来指定要加载的导航属性。
假设我们有两个实体类 Order 和 OrderItem,它们之间存在一对多的关系,一个订单可以包含多个订单项。下面是一个示例代码,展示如何使用预先加载来一次性加载订单及其所有订单项:
var order = dbContext.Orders
.Include(o => o.OrderItems) // 使用 Include 方法指定要加载的导航属性
.FirstOrDefault(o => o.Id == orderId);
在上面的代码中,我们首先通过 dbContext.Orders 获取 Orders 实体集合,并使用 Include 方法指定要加载的导航属性 OrderItems。然后,我们使用 FirstOrDefault 方法根据订单的 Id 获取第一个匹配的订单实体。EF Core 将会在查询时将所有相关的订单项也加载到内存中。
通过预先加载,我们可以在单个查询中获取订单及其所有订单项的数据,避免了多次查询的开销,提高了性能。这在需要同时访问和操作订单及其订单项数据时非常有用。
需要注意的是,预先加载可能导致加载过多的数据,造成性能问题。因此,我们应该谨慎使用预先加载,并根据具体情况进行权衡和优化。
4.显式转换(Explicit Conversion)
在查询时,可以使用 Cast 和 OfType 方法将查询结果强制转换为指定类型的实体。这种加载方式称为显式转换。
假设我们有两个实体类 Order 和 OrderItem,它们之间存在一对多的关系,一个订单可以包含多个订单项。下面是一个示例代码,展示如何使用显式转换来将查询结果转换为 Order 和 OrderItem 实体:
var orders = dbContext.Orders
.Include(o => o.OrderItems) // 使用 Include 方法指定要加载的导航属性
.ToList();
var orderItems = orders.SelectMany(o => o.OrderItems).ToList(); // 通过 SelectMany 方法获取所有订单项
var convertedOrders = orders.Cast<Order>().ToList(); // 将查询结果转换为 Order 类型
var convertedOrderItems = orderItems.Cast<OrderItem>().ToList(); // 将查询结果转换为 OrderItem 类型
在上面的代码中,我们首先通过 dbContext.Orders 获取 Orders 实体集合,并使用 Include 方法指定要加载的导航属性 OrderItems。然后,我们使用 ToList 方法将查询结果转换为列表,并使用 SelectMany 方法获取所有订单项。最后,我们使用 Cast 方法将查询结果分别转换为 Order 和 OrderItem 类型,并使用 ToList 方法将结果转换为列表。
通过显式转换,我们可以将查询结果转换为指定类型的实体,以便在进行进一步的操作和处理时,更好地进行类型匹配和类型转换。需要注意的是,在进行显式转换之前,我们应该确保查询结果中的实体类型与目标类型兼容。
5.原始 SQL 查询(Raw SQL Queries)
官网说明:https://learn.microsoft.com/zh-cn/ef/core/querying/sql-queries
除了使用 LINQ 查询语法外,EF Core 还支持执行原始 SQL 查询。原始 SQL 查询可以用于执行复杂的查询或利用数据库特定的功能。通过原始 SQL 查询,我们可以完全控制查询语句和结果集。
在订单和订单明细的例子中,我们可以使用原始 SQL 查询来执行自定义的 SQL 查询,并将查询结果转换为实体。
假设我们有两个实体类 Order 和 OrderItem,它们之间存在一对多的关系,一个订单可以包含多个订单项。下面是一个示例代码,展示如何使用原始 SQL 查询来获取 Order 和 OrderItem 实体:
var orders = dbContext.Orders.FromSqlRaw("SELECT * FROM Orders").ToList(); // 使用 FromSqlRaw 方法执行自定义的 SQL 查询
var orderIds = string.Join(",", orders.Select(o => o.Id)); // 构造订单 ID 列表字符串
var orderItems = dbContext.OrderItems.FromSqlRaw($"SELECT * FROM OrderItems WHERE OrderId IN ({orderIds})").ToList(); // 使用 FromSqlRaw 方法执行带参数的 SQL 查询
foreach (var order in orders)
{
order.OrderItems = orderItems.Where(oi => oi.OrderId == order.Id).ToList(); // 为每个订单设置订单项
}
在上面的代码中,我们首先使用 FromSqlRaw 方法执行自定义的 SQL 查询,获取所有的 Order 实体。然后,我们使用 LINQ 表达式构造订单 ID 列表字符串,以便在后续查询中使用。接着,我们再次使用 FromSqlRaw 方法执行带参数的 SQL 查询,获取所有的 OrderItem 实体。最后,我们使用 Where 方法为每个订单设置对应的订单项。
通过原始 SQL 查询,我们可以执行自定义的 SQL 查询,并获取指定的实体数据。需要注意的是,在构造 SQL 查询时,我们需要避免 SQL 注入攻击,并确保查询结果与实体类的属性匹配。此外,原始 SQL 查询可能会影响性能和可维护性,因此在实际使用中,应该谨慎使用。
综合对比
下表是 EF Core 提供的几种数据加载方式的综合对比:
数据加载方式 | 描述 | 优点 | 缺点 |
延迟加载(Lazy Loading) | 默认情况下,访问导航属性时自动执行查询加载关联数据。 | 简单易用,避免无谓的数据查询。 | 引发 N+1 查询问题,性能较差。 |
显式加载(Explicit Loading) | 使用 Entry 对象的 Collection 或 Reference 方法手动触发加载导航属性数据。 | 灵活控制加载行为,避免不必要的数据查询。 | 需要手动执行加载操作,较为繁琐。 |
急切加载(Eager Loading) | 使用 Include 方法指定要预先加载的导航属性,查询时一并加载相关实体数据。 | 提高查询性能,避免 N+1 查询问题。 | 查询结果包含大量无用数据,增加数据传输和内存消耗。 |
显式转换(Explicit Conversion) | 将原始 SQL 查询结果手动映射为实体对象。 | 灵活处理复杂查询需求,避免使用 LINQ 表达式。 | 安全性和可维护性较差,需要手动构造 SQL 查询语句。 |
原始 SQL 查询(Raw SQL Queries) | 执行自定义的原始 SQL 查询,并将查询结果转换为实体。 | 处理复杂的查询需求,灵活性高。 | 安全性和可维护性较差,需要手动构造 SQL 查询语句。 |
以上是对 EF Core 提供的数据加载方式的综合对比,根据具体的需求和场景,应选择适合的加载方式。延迟加载简单易用但性能较差,显式加载需要手动触发加载操作,急切加载可以提高查询性能但增加了数据传输和内存消耗,显式转换适用于复杂查询需求,原始 SQL 查询具有灵活性但安全性和可维护性较差。这些加载方式提供了不同的灵活性和性能特征,可以根据具体需求选择适合的加载方式,并从中获得最佳的性能和效果。
参考资料:EF Core 文档:https://docs.microsoft.com/ef/core/。