如何使延迟加载与 dbcontext 一起工作

问题描述 投票:0回答:1

在此处使用 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();
c# asp.net-core entity-framework-core lazy-loading
1个回答
0
投票

“你一直用这个词。我不认为这意味着你所认为的那样。”

  • 伊尼戈蒙托亚,公主新娘。

惰性加载与急切加载的速成课程。

延迟加载:启用此功能并访问导航属性时,如果尚未从数据库加载,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 获取角色实体,进行更改,然后保存。

© www.soinside.com 2019 - 2024. All rights reserved.