【问题标题】:Secondary Db Context in ASP.NET CORE EFASP.NET CORE EF 中的辅助数据库上下文
【发布时间】:2016-10-17 12:03:28
【问题描述】:

我有一个发送通知的服务,它需要一个数据库连接来查找订阅。我还有一个控制器(可能更多),它执行一些逻辑并发送通知。

这个问题是,因为DI 它使用DbContext 的相同实例,所以在相同的上下文中重新使用DataReader 时会抛出错误(可以理解)。

我真的很想在不启用 DbConnectionString 中的 MARS 标志的情况下执行此操作。鉴于控制器不能使用.ToList() 或没有跟踪并且“内部”NotificationService 需要查找数据库——这甚至可能吗?

public class NotificationSystem
{
     private readonly DbContext context;
     public NotificationSystem(DbContext context) { this.context = context;}

     public void SendNotification(string username){
       var subscriptions = context.subscriptions.where(u => u.username == username); 
       // Do some notification stuff
     } 
}

还有一个简单的控制器

public class SendRemindersController : Controller
{
    private readonly DbContext _context;
    private readonly NotificationSystem _notificationSystem;

    public SendRemindersController(DbContext context, NotificationSystem notificationSystem)
    {
        this._context = context;
        this._notificationSystem = notificationSystem;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var reminders = _context.Reminders.Where(r => r.Sent == false && r.RemindAt < DateTime.UtcNow);

        foreach (var reminder in reminders)
        {
            await _notificationSystem.SendNotificationToUser(reminder.UserId);
            reminder.Sent = true;
        }

        await _context.SaveChangesAsync();
        return Ok();
    }
}

还有startup.cs(是的,我知道我没有使用过接口,稍后会对其进行重构)。

services.AddDbContext<DbContext>(options => options.UseSqlServer(connection));
services.AddTransient<NotificationSystem, NotificationSystem>();

更新

这个问题是有缺陷的,因为我的错误印象是 .ToList/.ToArray 也将实体与上下文分离。事实上,这些不会分离,只会执行查询。

【问题讨论】:

  • 如果您对reminder 字段使用延迟初始化,或者SendNotificationToUser 试图在迭代中间执行查询,则会出现此错误。确保在操作开始时加载所有必需的数据,例如使用Include() 语句。
  • 顺便说一句,您应该有一个从提醒到用户或订阅的关系,而不是使用像用户名这样的 ID 来链接它们。
  • @PanagiotisKanavos 在实际代码中有:) 我只是想举个例子。
  • 在这种情况下,您不需要第二次查询。你可以走的关系。在任何情况下,您都应该尝试在开始时加载所需的所有数据,以避免此错误和多次往返数据库
  • 这是数据库的工作,例如hierarchyid。以这种方式查找节点的父节点是微不足道的。 EF 通过an extension 支持hierarchyid。这将使您的性能至少提高 100 倍(对所有父母进行 1 次查询,而不是每个项目 3-4 次递归)

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


【解决方案1】:

这是因为您使用相同的DbContext 来执行多个并发事务。如果像这样在这行代码中添加.ToListAsync()

var reminders = await _context.Reminders
  .Where(r => r.Sent == false && r.RemindAt < DateTime.UtcNow)
  .ToListAsync();

它将立即检索所有提醒,然后循环内的代码(在此语句之后)可以使用DbContext,而DbContext 不会抛出异常,因为仍在迭代活动结果集。

【讨论】:

  • 我明白,但我不能使用 .ToList 或没有跟踪(“鉴于控制器不能使用 .ToList() 或没有跟踪并且“内部”NotificationService 需要查找数据库 - 是这甚至可能吗?”)因为一些使用该服务的控制器需要更新他们的实体。
  • @JohnMitchell - ToList 和 AsNoTracking 是两个不相关的东西。您可以在这种情况下使用 ToList 并且仍然能够以与正常相同的方式更新实体。
  • @JohnMitchell 为什么你不能使用它?你已经在这样做了,没有意识到,以尽可能慢的方式(即同步)。您可能也在尝试延迟加载数据,从而导致 N+1 查询问题。解决方法是在开始时加载所有必要的数据(查找也是)。这将导致 更快 执行,因为您只进行 1 次调用而不是 N
  • @JohnMitchell 不,不会。它将执行查询并在IList 中返回结果。不过,如果您不打算将新项目添加到列表中,我更喜欢 ToArrayAsync
  • @JohnMitchell - 很划算。如果您想要检索分离的实体,有一个名为 .AsNoTracking() 的命名扩展可以做到这一点。 AFAIK 没有其他本机扩展方法可以隐式执行此操作。
猜你喜欢
  • 1970-01-01
  • 2021-01-06
  • 2015-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-05
  • 2018-02-03
  • 1970-01-01
相关资源
最近更新 更多