我正在研究延迟加载。我不明白延迟加载是如何工作的,我应该做些什么来改变它吗?
以下是我的第一个案例:我有User
,User roles
和Roles
类。用户与角色具有多对多关系。所以我创建了用户角色类来处理这种关系。用户不包括所有情况下的角色(* 不介意UserRole中的Id
*)。
用户
public class User : IUser
{
public bool IsActive { get; set; }
public int Id { get; private set; }
public string Email { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public string? Phone { get; set; }
public List<UserRole> UserRoles { get; set; }
}
用户角色
public class UserRole : IEntity
{
public int Id { get; private set; }
[ForeignKey(nameof(User))]
public int UserId { get; set; }
public User User { get; set; }
[ForeignKey(nameof(Role))]
public int RoleId { get; set; }
public Role Role { get; set; }
}
角色
public class Role : IEntity
{
public int Id { get; private set; }
public string Name { get; set; }
}
以下是我的第二个案例:我有一个Article
类,它与User
有关系。在这种情况下,Article
使用Creator.Email
(User
)或任何情况下的创建者名称,这意味着使用Article
的任何查询都包括User
。
文章
public class Article : IEntity
{
public Article() => CreatedAt = DateTime.Now;
public bool IsDeleted { get; set; }
public int Id { get; private set; }
public string Title { get; set; } = "Başlık";
public string Content { get; set; } = "İçerik";
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
[ForeignKey(nameof(User))] public int CreatorId { get; set; }
[ForeignKey(nameof(User))] public int? DeletedBy { get; set; }
public User? Creator { get; set; }
public List<ArticleCategory> ArticleCategories { get; set; }
}
所以我的问题是我应该做些什么来延迟加载这些相关的属性吗?
如果是-我该怎么办?如果不是-这是如何与EF核心工作?
例如User
,它是从数据库返回数据并将其保存在内存中直到被请求,还是直到被请求才进入UserRoles和Roles表?
如果两者都没有,它实际上是如何工作的?
下面是一个例子,我可以从数据库中获取所有数据,在一个非常简单的方式:
var users = context.Users
.Include(u => u.UserRoles)
.ThenInclude(ur => ur.Role);
在某些情况下,即使我不请求用户的角色,它们会从数据库中获取吗?如果是,它们会保存在内存中吗?
2条答案
按热度按时间ykejflvf1#
Lazy<T>
是一个框架提供的类,用于支持任何自定义开发人员定义的逻辑/函数的延迟初始化(通常用于多线程环境,但不限于此)。与EF Core无关。另一方面,EF Core中的Lazy Loading数据是在不使用(至少在面向用户的API/约定中)
Lazy
的情况下完成的。在EF中有两种主要的延迟加载“风格”--有代理和没有代理。第一种方法依赖于使用特殊的包,并且需要相应的属性是虚拟的:这导致生成特殊的代理类,这些代理类将推迟相关数据的加载,除非需要(即以某种方式访问)。举例来说:
这两个概念是相关的,但仍然有点不同。
P.S.
Lazy
解决的问题的子集。5cnsuln72#
EF中的延迟加载会有一个相当糟糕的 Package ,因为当你不了解发生了什么以及EF在幕后做了什么时,你会发现你的代码非常依赖延迟加载,这会导致一个相当严重的性能问题。
在EF中加载相关数据的两个方面是延迟加载和急切加载。Eager loading是用
Include
完成的,而lazy loading通常是用代理完成的,尽管EF Core提供了第二种选择,您自己的代码可以控制管理lazy请求。每种选择都有潜在的陷阱需要考虑。延迟加载是延迟查询的一种形式。假设你想加载一个100个帖子的列表,每个帖子可以在类似WPF应用程序的东西中有0到多个评论。您只想列出Posts,因此可以从dbContext.Posts中获取并显示它们,然后当您选择展开其中一个Posts时,您希望显示其Comments。急切加载会预先加载所有帖子的所有评论,这是相当过分的,所以在这种情况下,懒惰加载只会在每个帖子被扩展时获取评论。这完全是由EF在幕后完成的。当您无意中或错误地引入代码“触及”延迟加载的属性时,就会出现这种情况。以加载Posts为例,您决定添加一个列来使用
post.Comments.Count
显示Comment计数。当渲染代码遍历每个Post时,它会访问Comments,这会为每个Post触发延迟加载。在开发测试数据库时,您可能不会注意到它,但在生产环境中,随着系统的增长,它很快就会引起注意。这可以在DB Profiler中看到,因为您会看到发送到数据库的大量查询。例如,如果你使用给定的日期范围获取了100个帖子,你会看到这样的查询:100个SELECT语句来获取每个帖子的评论。这是不好的,并与更多的参考得到“触摸”化合物。这就是令人恐惧的SELECT N+1。
通常,当被这样的延迟加载所刺痛时,推荐的解决方案是使用渴望加载:
这将再次在一个SQL SELECT语句中加载这100个帖子及其评论。例如:
Include
和嵌套的ThenInclude
,事情会很快失控,EF Core会警告你。上面的例子使用了即时加载,将生成一个在Posts和Comments之间执行JOIN的查询。为了在一个查询中加载所有帖子和评论,查询必须返回两个表中的所有列。当只加载Posts时,返回的总数据大小为:当按注解联接时,返回的总数据大小为:
查询为每个注解返回一行。如果每个帖子平均有5条评论,为了得到100个帖子,我们将返回500行,其中每行都有来自**评论及其帖子的所有列。当你连接实体时,这很容易导致查询中返回更多的数据。
EF Core以
AsSplitQuery()
的形式提供了一个解决方案,它通过对帖子和评论运行单独的查询来工作,所以你最终会得到这样的结果:第二个SELECT看起来很像急切加载的SELECT,但它只是返回Comments中的列。这在大多数情况下都可以工作,但是EF仍然需要将所有这些相关的实体缝合在一起,这可能需要一些时间,并且它确实有局限性。在使用拆分查询时,诸如排序、分页和汇总相关数据之类的事情可能会出现问题,因此它是一个可以提供帮助的工具,但不应依赖于它。
回到我们想要一个评论计数的例子……获取所有评论只是为了得到一个可靠的计数是一个很大的浪费。即使我们需要相关实体的一些细节,急于加载整个实体也会浪费时间和内存。这就是投影在数据返回时更具选择性的地方。投影是查看实际需要的数据并使用
Select
填充对象以满足该需求而不是返回实体的地方。投影的优点是EF可以产生更高效的查询,并避免跟踪缓存因跟踪示例而膨胀。例如,如果我们想显示一个帖子列表,包括标题、发布日期、作者姓名和评论数,我们可以创建一个PostSummaryViewModel,如下所示:然后读它:
结果将是EF生成一个查询来获取所请求的数据,并根据需要自动加入表,但只返回所需的列。请注意,我们引用的评论和作者引用从我们的帖子,但不需要
Include
他们,EF解决了自动通过这些导航属性的引用。这确实意味着,如果我们想实现一些逻辑,比如扩展一个帖子加载它的评论,或者显示更多的信息,我们需要一个单独的调用来从DbContext中获取数据,所以我们不能简单地依赖于懒惰的加载拐杖。但总的来说,好处应该超过方便的损失。延迟加载、急切加载和投影都是EF中可用的工具,它们在某些情况下都提供了好处,但也需要考虑成本。