在此处使用 LazyLoadingProxies 配置 DbContext 后,我的 LazyLoadingProxies 不起作用:
services.AddDbContextPool<DbContext, ApplicationDbContext>((provider, builder) =>
{
var configuration = provider.GetRequiredService<IConfiguration>();
var options = provider.GetRequiredService<IOptions<SqlServerRetryOptions>>();
builder
.EnableDetailedErrors(true)
.EnableSensitiveDataLogging(true)
.UseLazyLoadingProxies(true) // => If UseLazyLoadingProxies, all of the navigation fields should be VIRTUAL
.UseSqlServer(
connectionString: configuration.GetConnectionString("ConnectionStrings"),
sqlServerOptionsAction: optionsBuilder
=> optionsBuilder.ExecutionStrategy(
dependencies => new SqlServerRetryingExecutionStrategy(
dependencies: dependencies,
maxRetryCount: options.Value.MaxRetryCount,
maxRetryDelay: options.Value.MaxRetryDelay,
errorNumbersToAdd: options.Value.ErrorNumbersToAdd))
.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.GetName().Name));
});
这是角色实体:
public class Role : DomainEntity<int>
{
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
我不知道为什么角色的用户也包含在内,即使我没有在代码中使用 Include
public List<Role> FindAll()
=> context.Role.ToList();
“你一直用这个词。我不认为这意味着你所认为的那样。”
惰性加载与急切加载的速成课程。
延迟加载:启用此功能并访问导航属性时,如果尚未从数据库加载,EF 将立即从数据库加载。
预加载:告诉 EF 立即加载与父数据相关的数据。
示例:使用新的 DbContext。
预加载:
public List<Role> FindAll()
{
return context.Role
.Include(r => r.Users)
.ToList();
}
这将生成如下 SQL 语句:
SELECT r.*, u.* FROM Roles r
INNER JOIN UserRoles ur ON r.RoleId = ur.RoleId
INNER JOIN Users u ON ur.UserId = u.UserId
延迟加载:
public List<Role> FindAll()
{
return context.Role
.ToList();
}
这最初会生成一个 SQL 语句,例如:
SELECT r.* FROM Roles r
并且只关注填充角色。但是,EF 会为 Role.Users 集合分配一个代理,因此只要有任何代码,包括调试器(如果您正在检查数据是否已加载),更重要的是,诸如序列化器之类的东西“触摸” “角色。用户导航属性,您将看到弹出的查询,例如:
SELECT u.* FROM Users u
INNER JOIN UserRoles ur ON u.UserId = ur.UserId
WHERE ur.RoleId = 1
这就是延迟加载可能成为一个大问题的地方,因为与序列化器之类的交互会迭代每个角色并“接触”每个角色上的用户集合。想象一下,如果您加载 100 个角色并将它们序列化..您会得到:
SELECT u.* FROM Users u
INNER JOIN UserRoles ur ON u.UserId = ur.UserId
WHERE ur.RoleId = 1
SELECT u.* FROM Users u
INNER JOIN UserRoles ur ON u.UserId = ur.UserId
WHERE ur.RoleId = 2
SELECT u.* FROM Users u
INNER JOIN UserRoles ur ON u.UserId = ur.UserId
WHERE ur.RoleId = 3
....
SELECT u.* FROM Users u
INNER JOIN UserRoles ur ON u.UserId = ur.UserId
WHERE ur.RoleId = 100
预加载场景仅在一次调用中加载所有 100 个角色及其关联的用户。 (或者如果使用
AsSplitQuery()
则调用两次)
然后想象一下,当用户被序列化时,用户实体引用了另一个实体,这会为每个用户的每个相关实体触发 SQL 语句。
因此,EF Core 中默认禁用延迟加载。 (它在 EF .Net Framework 中默认启用)应该避免它,除非您有充分的理由启用它并且小心使用它。
对于 EF 如何管理跟踪引用,有一个明显的警告。如果您碰巧引用了
DbContext
已跟踪的相关数据,那么在使用延迟加载时,或者甚至在禁用延迟加载并且您不使用预先加载时,这些引用将自动填充。例如,如果我有执行此操作的代码:
var users = context.Users.ToList();
稍后致电:
var roles = context.Roles.ToList();
无论我是否启用了延迟加载,当
DbContext
返回给我角色时,它都会自动关联适用于每个角色的任何/所有用户。当人们禁用延迟加载时,这可能会让人们感到困惑,因为他们会发现相关数据被部分加载,或者在某些情况下加载,而在其他情况下他们忘记可靠地预先加载。这是保持 DbContext
实例短暂存在的重要原因之一,因为 DbContext
跟踪的实体越多,在加载/返回更多数据时筛选潜在关系所需的时间就越长。
读取大量数据(例如搜索结果)时的最佳解决方案是使用 ViewModel 和投影,而不是读取整个实体:
var roles = context.Role
.Select(x => new RoleSummaryViewModel
{
RoleId = x.Id,
Name = x.Name,
UserCount = x.Users.Count()
}).ToList();
这样做的优点是,您构建的查询仅返回您实际需要的数据,并且
DbContext
最终不会跟踪任何内容,它只是填充这些视图模型。 (只是不要混合视图模型和实体,例如在视图模型中包含用户实体的集合)然后,如果您想实际执行诸如更新特定角色之类的操作,请按 ID 获取角色实体,进行更改,然后保存。