【问题标题】:EF Core Linq Where nested database callEF Core Linq Where 嵌套数据库调用
【发布时间】:2019-09-10 12:24:04
【问题描述】:

我想使用 EF Core 执行数据库查询。 GetAvailableUsers 方法在.Where(...) 条件中调用另一个方法,该方法也访问DbContext。当我这样做时,我收到以下错误:

A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.

FirstService.cs

private MyDbContext _dbContext;
private ISecondService _secondService;

public class FirstService(MyDbContext dbContext, ISecondService secondService)
{
    _dbContext = dbContext;
    _secondService = secondService;
}

public async Task<List<AppUser>> GetAvailableUsers()
{
    return await _dbContext.AppUser
        .Where(x => x.IsActive)
        .Where(x => _secondService.HasRole(x.Id, 3))
        .ToListAsync();
}

SecondService.cs

private MyDbContext _dbContext;

public class SecondService(MyDbContext dbContext)
{
    _dbContext = dbContext;
}

public bool HasRole(int userId, int roleId)
{
    // ... some complex logic

    return _dbContext.AppUserRoles
        .Any(x => x.UserId == userId && x.RoleId == roleId);
}

我在 Startup.cs 中注册了 DbContext:

services.AddEntityFrameworkNpgsql().AddDbContext&lt;DbContext&gt;(options =&gt; options.UseNpgsql(Configuration.GetConnectionString("abc")));

我怎样才能摆脱错误?是否有任何解决方法可以达到相同的结果?如果我将 DbContext 注册为瞬态会有什么后果?

【问题讨论】:

  • 从你的代码中,你想得到所有拥有roleId=3IsActive=true的用户吗?你是如何定义你的模型的?它们之间的关系是什么?

标签: c# entity-framework asp.net-core


【解决方案1】:

重写这个

return await _dbContext.AppUser
    .Where(x => x.IsActive)
    .Where(x => _secondService.HasRole(x.Id, 3))
    .ToListAsync();

包含角色然后你可以直接访问它,也返回任务不创建状态机。这也将创建 1 个查询而不是 N+1。

public Task<List<AppUser>> GetAvailableUsers()
{
    return _dbContext.AppUser
            .Include(x => x.Roles)
            .Where(x => x.IsActive && x.Roles.Select(y => y.RoleId).Contains(3))
            .ToListAsync();
}

Why AddDbContext is Scoped

【讨论】:

  • 你为什么要加Include(x =&gt; x.Roles)
  • @Jota.Toledo 因为这是第二个服务试图做的事情。
  • 也许值得强调一下,这将导致单个查询而不是 N+1 个查询?
  • HasRole 是一个复杂的函数,包含一些额外的逻辑。我应该在我的问题中说明这一点。因此,我不能使用您的答案,因为您没有在任何地方调用该函数。
  • @Mark 包含复杂的逻辑会很有用,因为现在看来您的执行逻辑和查询都纠结在一起。这就是导致您实施的原因。但现在看来,返回结果并不取决于那个“复杂的逻辑”
【解决方案2】:

是否有任何解决方法可以达到相同的结果?

你可以得到AppUser中的所有UserId,然后foreach这个UserId判断HasRole是否返回true。这里是一个smiple demo:

型号:

public class Role
{
    public int RoleId { get; set; }
    public string RoleName { get; set; }
    public List<AppUserRole> AppUserRole { get; set; }
}
public class AppUser
{
    [Key]
    public int UserId { get; set; }
    public bool IsActive { get; set; }
    public List<AppUserRole> AppUserRole { get; set; }
}
public class AppUserRole
{
    [Key]
    public int Id { get; set; }
    public int UserId { get; set; }
    public AppUser AppUser { get; set; }
    public int RoleId { get; set; }
    public Role Role { get; set; }
}

行动:

public async Task<List<AppUser>> GetAvailableUsers()
{
        var userlist =new List<AppUser>();
        var userIds =await _dbContext.AppUser.Select(u => u.UserId).ToListAsync();
        foreach (var userid in userIds)
        {
            var flag = _secondService.HasRole(userid, 3);
            if (flag)
            {
                var user = await _dbContext.AppUser.Where(x => x.IsActive).Where(x => x.UserId == userid).FirstOrDefaultAsync();
                userlist.Add(user);
            }  
        }           
        return userlist;
}

【讨论】:

    【解决方案3】:

    我通过在 SecondService.cs 中创建新的 DbContext 解决了这个问题。

    public bool HasRole(int userId, int roleId)
    {
        MyDbContext newContext = new MyDbContext(...);
    
        using (newContext)
        {
            return newContext.AppUserRoles
                .Any(x => x.UserId == userId && x.RoleId == roleId);
        }
    }
    

    【讨论】:

      【解决方案4】:

      是否有任何解决方法可以达到相同的结果?

      我建议三种解决方案之一:

      1. MyDbContext 的注册从Scoped 切换为Transient。这将确保这两种方法使用两个 不同 数据库连接。
      2. 执行.Where(x =&gt; _secondService.HasRole(x.Id, 3)) 之后 ToListAsync
      3. 重组您的代码,以便执行单个 SQL 查询(请参阅 Anton 的答案作为起点)。

      选项 1 和 2 都有效rewrite the query avoiding nested invocations。 1 和 2 的缺点是它们都将执行许多数据库查询,并可能通过网络带来比所需更多的数据。

      选项 3 是您最好的长期解决方案(因为它可以更好地扩展),但是如果没有看到 HasRole 的全部内容,很难在这里给出具体建议。

      将 DbContext 注册为瞬态的利弊是什么?

      它们在很大程度上与任何其他类型相同。如果该类型不是线程安全的(并且您计划在单个 Web 请求的上下文中从多个线程与其交互),或者您计划以它不支持的方式使用它(例如多个 DB 请求在在同一个数据库上下文(就像您现在所做的那样)上同时进行)然后瞬态将起作用,而作用域则不会。

      作为general ruleScoped 更适合DB Context。

      如果我将 DbContext 注册为瞬态会有什么后果?

      试试看。我强烈怀疑异常将停止发生。如果不查看整个应用程序,就不可能提供更具体的指导。唉。

      【讨论】:

        猜你喜欢
        • 2021-11-23
        • 2020-08-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-26
        • 1970-01-01
        • 2021-07-05
        相关资源
        最近更新 更多