Ala*_*laa 2 sql entity-framework .net-6.0
我搜索了很多关于急切加载、显式加载和延迟加载之间的区别。我想了解这三者的行为,以便能够决定何时使用每一个。因此,经过大量搜索后,我只是想确保我理解正确。因为,如果我误解了什么,我会很感激有人纠正我。
1_预加载:
急切加载将在单个查询中检索,因为它将被转换为包含join的 sql 查询操作的 SQL 查询。这允许缓存检索到的数据,从而提高应用程序性能。这就像内存消耗和数据库行程(数据库服务器和应用程序服务器之间)之间的权衡。
因此急切加载对性能有好处,但如果我想检索大量数据,那么它不是一个好的选择:例如,如果我有一个用户列表,每个用户都有一个评论列表。在这种情况下,急切加载并不是一个好的选择,因为它会消耗大量内存。
2-延迟加载
延迟加载不会像急切加载一样转换为包含join 的sql 查询,而是会转换为一组选择查询,例如,如果我有一个用户列表,每个用户都有一个评论列表,则翻译后的 sql 查询将类似于这:
Select * from Users (to retrive all the users)
//for each user
Select * from Reviews where Reviews.UserId =userId;
//will need 1+n sql quries, where n is the number of users
Run Code Online (Sandbox Code Playgroud)
这不会导致内存的大量消耗,但会增加数据库行程(数据库服务器和应用服务器之间)。所以在性能上不如急切加载,但是对于大数据量,最好使用延迟加载,避免大量消耗内存。所以对于上面的例子,如果用户数量很大,使用延迟加载比急切加载更好。
3-显式加载:
显式加载类似于延迟加载,但它只适用于单个实体。因此,如果我想在列表上使用它,我必须创建一个 for 循环来迭代每个实体。另外,如果我使用延迟加载,每次我想从数据库检索实体时,即使我不想,也会加载相关数据。因此,如果我希望相关数据仅在特定情况下加载,显式加载可能是一个不错的选择。
因此,举例来说,假设我只想在传递用户的 id 时获取用户的相关评论,最好的方法是使用:_explicit loading:因为延迟加载将为用户的每次检索加载相关评论从数据库(在这种情况下,我只需要在传递 id 时检索它)
public async Task<User> GetUserById(int id)
{
var user = _context.User.Find(id);
var userWithReviews =context.Entry(user).
.Collection(b => b.Reviews).load();
return userWithReviews;
}
Run Code Online (Sandbox Code Playgroud)
_或使用简单的选择:因为急切加载使用连接(从中我知道连接比选择操作成本更高)
public async Task<UserAndReviews> GetUserById(int id)
{
UserAndReviews userAndReviws = new UserAndReviews();
var user = await _context.User.FindByIdAsync(id);
var Reviews =await context.Reviews.where(r=>r.UserId =id).ToListAsync();
userAndReviws.user = user;
userAndReviws.Reviews =Reviews;
return userAndReviws;
}
Run Code Online (Sandbox Code Playgroud)
这是我经过大量搜索后所理解的,如果我误解任何事情,我将不胜感激。
您走在正确的道路上, MS:Learn - 加载相关数据中的摘要有最简单的措辞:
然而,它确实错过了最重要的数据检索模式“投影操作”。我们将在最后讨论...
您是正确的,即热加载是一种权衡,但我们通常更喜欢它而不是延迟加载。在分布式和基于云的应用程序中,数据库连接和从数据库请求数据通常是性能瓶颈。我们首先要考虑的是带宽和建立连接的开销。我们通过在一次调用中检索更大的数据集并将这些数据有效地缓存在内存中来减少对数据库的直接请求数量。
您应该只检索过滤后的数据集,而不是尝试将整个表或数据库加载到内存中,除非这是您明确的愿望。但很多情况下Eager实际上可以提供更高的网络性能体验。重要的是,从开发的角度来看,一旦加载数据,我们就可以继续处理记录,而无需访问数据库上下文。如果需要,这允许逻辑与数据访问分离,甚至取消引用。
确实,预加载使用连接,但在许多情况下,认为连接成本高昂并没有帮助。从 SQL 的角度来看,它们不是,但从 EF 的角度来看,存在惩罚,不是因为使用了 a,JOIN而是因为历史上查询会在扁平化或非规范化的单个结果集中检索。
因此,返回 3 个用户(每个用户有 2 个评论)的查询实际上会返回 6 行,其中用户字段合并到与评论相同的记录中:
| 用户身份 | 用户名 | 评论.Id | 评论.UserId | 评论.笔记 |
|---|---|---|---|---|
| 1 | 约翰 | 1 | 1 | 约翰的第一次评论 |
| 1 | 约翰 | 2 | 1 | 约翰的第二次评论 |
| 2 | 安妮 | 3 | 2 | 安妮的第一次评论 |
| 2 | 安妮 | 4 | 2 | 安妮的第二次评论 |
| 3 | 杰米 | 5 | 3 | 杰米的第一次评论 |
| 3 | 杰米 | 6 | 3 | 杰米的第二次评论 |
如果用户有很多字段,或者有多层嵌套连接,则传输的数据会迅速膨胀,从而包含大量冗余字节或信息。Join本质上并不是坏事,除非你正在做一个非常简单的SELECT *. 数据库本身在通过连接查询数据方面非常高效,传输回应用程序逻辑及其反规范化可能会对性能产生影响。
EF 有一个特定的优化可以帮助解决这个性能问题,即分割查询。这一简单的技术实际上会将 C# 表达式转换为多个 SQL 查询,每个查询对应一个表。然后 EF 将从这些单独的结果中生成图表。它不适用于所有查询,但在许多简单的情况下,这是低代码解决方案,可为您提供连接的 SQL 和数据完整性优势,同时最大限度地减少 EF 默认传输开销。
我个人的经验是,从 EF Core 7 开始,急切加载在
AsSplitQuery()性能和数据库调用频率之间提供了良好的折衷。我永远不会推荐延迟加载,并且仅在可以测量性能优势并且额外的代码维护值得付出努力的显式场景中使用显式加载。
在初始调用时,只会从数据库中检索根级实体。
// C# - Select all users
var users = dbContext.Users.ToList();
Run Code Online (Sandbox Code Playgroud)
SELECT * FROM Users;
Run Code Online (Sandbox Code Playgroud)
然后,在您的应用程序逻辑中,在每个实体的基础上,当您第一次访问导航属性时,将直接从数据库查询该属性:
// C# - Assume there are 3 users
foreach(var user in users.Take(3))
{
var reviews = users.Reviews.ToList();
}
Run Code Online (Sandbox Code Playgroud)
SELECT * FROM Reviews WHERE Reviews.UserId = @userId;
SELECT * FROM Reviews WHERE Reviews.UserId = @userId;
SELECT * FROM Reviews WHERE Reviews.UserId = @userId;
Run Code Online (Sandbox Code Playgroud)
如果根集合中没有很多实体并且您的逻辑仅在特定条件下访问导航属性,那么这可能是理想的,但通常这种模式会导致性能泄漏。
这不是意见,而是事实。我并没有说性能不好,尽管这是我的经验,但总的来说,您的代码将从代码中不立即明显的多个位置查询数据库,并且通常在高频场景中意味着每次第一次访问惰性加载必须等待连接数据库和等待响应的开销,然后代码才能继续。
即使您忽略性能影响,对数据库的查询也很容易受到暂时性错误的影响,这些错误可能包括并发和请求限制问题以及服务器中的行或表内的线程、逻辑块和锁。这意味着存在访问任何导航属性可能因数据库问题而失败的风险,因此建议您实施暂时性错误重试策略,或者准备好直接在代码中捕获和处理这些问题。
如果您使用云托管数据库,通常必须遵守请求频率限制。与其他加载选项相比,针对使用延迟加载的实体的逻辑达到请求限制的风险要高得多。
这与延迟加载非常相似,除了我们显式控制何时查询数据库并且我们可以优化该查询。
延迟加载和显式加载都对单个实体进行操作。
您的示例已经足够好了,但主要的长期好处是,当您阅读代码时,很明显正在加载什么以及何时加载。这允许您优化逻辑并考虑数据访问。如果启用延迟加载的实体被传递给处理函数,则访问属性将导致数据库查询可能并不明显,也不会意识到查询可能会导致数据库查询。失败。
显式加载提供了一些确定性和显式逻辑块来包装异常处理逻辑。它还允许我们加载多个嵌套级别(可能通过急切加载)并应用过滤器。显式加载通常用于控制器或存储库模式,其中实体引用被传递到方法,但该方法所需的相关属性未加载或假定未加载。
EF 中所有上述数据加载模式的最大问题是,您将根和相关实体中的所有字段加载到应用程序逻辑层......因此加载到内存中。您正在违反 SQL 应用程序设计的基本规则之一,SELECT *即您要避免的昂贵操作。
如果彼得·潘所说的“每次你说你不相信仙女,一个仙女就会死去”是正确的。那么这也是真的:“每次查询
SELECT *,都会有一个 DBA 死去”
过滤您需要的行非常重要,但特别是如果您想要将许多实体和许多相关属性加载到内存中,那么我们希望确保只带回我们的逻辑需要的特定属性。
投影可以是匿名类型或具体类型,并且在 EF 中考虑起来可能会更复杂,但该概念实际上与 DTO 或 ViewModel 相同。我们将数据准备为仅包含所需字段的单一形式。
投影对于数据的只读逻辑处理最有意义,但它们要求您预先考虑逻辑的数据要求,并且可能会限制下游处理。因此,预测会阻碍极端敏捷编程。
我个人的设计方法是首先鼓励并更喜欢预加载,然后一旦逻辑经过测试并且稳定,如果需要解决性能问题,我会将该逻辑过程转换为使用Projections。有时您可以预测需要哪些字段,但作为在更大的团队中工作的过程,稍后添加预测可以让我们更快地达到里程碑,同时减少回归。
| 归档时间: |
|
| 查看次数: |
1009 次 |
| 最近记录: |