使用 DBContext 错误调用多个 Hangfire 作业

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

我正在使用 Hangfire 来安排 powershell 任务。具体来说,创建一个作业来安排转发,然后删除转发(电子邮件)。在创建转发任务中,作业 ID 被存储到 SQL 数据库中,在删除转发任务中,数据库条目被删除(因此我可以显示一个显示计划转发的表格)。在删除转发任务时,hangfire 第一次失败,然后第二次成功。它因以下错误而失败:

无法访问已处置的上下文实例。此错误的一个常见原因是处理从依赖项注入解析的上下文实例,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文实例上调用“Dispose”,或将其包装在 using 语句中,则可能会发生这种情况。如果你使用依赖注入,你应该让依赖注入容器处理上下文实例。

System.InvalidOperationException:尝试在配置上下文实例时使用它。 DbContext 实例不能在“OnConfiguring”中使用,因为此时它仍在配置中。如果在上一个操作完成之前在此上下文实例上启动第二个操作,则可能会发生这种情况。不保证任何实例成员都是线程安全的。

我已经尝试制作它正在调用的服务范围和瞬态,但我得到了同样的错误。

在Hangfire中定时运行的方法很简单

public async Task RemoveForwardByScheduleAsync(ScheduledForward scheduledForward)
    {
        var forward = await _context.ScheduledForwards.FirstOrDefaultAsync(x => x.StartJobId == scheduledForward.StartJobId);

        if (forward != null)
        {
            await RemoveForwardAsync(forward.FromId);

            _context.ScheduledForwards.Remove(forward);
            await _context.SaveChangesAsync();
        }
    }

第二次成功的原因貌似是因为第一次确实起作用了,把item从数据库中移除了,所以第二次运行的时候forward == null 因为已经不在数据库中了'点击 savechangesasync 方法(这就是触发 dbcontext 错误的原因)。

更新:我错了。它实际上不是第一次工作,除非它是要运行的作业列表中的第一个作业。我了解到,这只会发生在与其他作业同时安排的作业上,并且第一个运行的作业会成功。然后重试下一个运行成功的作业。当计划运行使用相同服务的多个作业时,这几乎就像 Hangfire 不会为每个作业调用一个新实例。

public class PowerBlazorDbContext : DbContext
{

    public PowerBlazorDbContext(DbContextOptions<PowerBlazorDbContext> options) : base(options)
    {

    }

    public DbSet<ScheduledForward> ScheduledForwards => Set<ScheduledForward>();

}


public class PowerService : IPowerService
{

    private static PowerBlazorDbContext _context;
    public PowerRun(PowerBlazorDbContext context)
    {
        _context = context;
    }

我已经尝试过 scoped 和 transient,无论我使用哪种服务,都得到相同的结果(iPowerRun 和 IPowerService 是彼此的副本)。

builder.Services.AddScoped<iPowerRun, PowerRun>();
builder.Services.AddTransient<IPowerService, PowerService>();

这里是创建 hangfire 作业的地方。

public async Task<ScheduledForward> ScheduleForward(MailboxUser forwardFrom, MailboxUser forwardTo,bool sendToBoth, DateTime startTime, DateTime stopTime)
    {
        DateTimeOffset start = new DateTimeOffset(startTime);
        DateTimeOffset stop = new DateTimeOffset(stopTime);
        var scheduled = new ScheduledForward()
        {
            FromDisplay = forwardFrom.DisplayName,
            FromId = forwardFrom.Identity,
            ToId = forwardTo.Identity,
            ToDisplay = forwardTo.DisplayName,
            SendToBoth = sendToBoth,
            StartTime = startTime,
            EndTime = stopTime,
            StartJobId = "",
            StopJobId = "",
        };
        
        scheduled.StartJobId = BackgroundJob.Schedule<PowerService>(x => x.ForwardMailboxAsync(forwardFrom.Identity, forwardTo.Identity, sendToBoth), start);
        scheduled.StopJobId = BackgroundJob.Schedule<PowerService>(x => x.RemoveForwardByScheduleAsync(scheduled), stop);

        await _context.ScheduledForwards.AddAsync(scheduled);
        await _context.SaveChangesAsync();
        return scheduled;
    }

我真的认为这是 Hangfire 的一个问题,它没有为每个作业调用一个新的服务实例,但我可能是错的。发生此问题时,它实际上也会中断 Blazor 对数据库的访问,并且刷新页面会产生与我在 Hangfire 中看到的相同的错误。

c# asp.net-core blazor hangfire
1个回答
0
投票

好的,它似乎正在使用 DBContext Factory 工作。我不确定我是否正确实施了这一点。如果有更好的方法请评论。这会按照我实现的方式正确处理 dbcontext 吗?

我将 Factory 添加到 program.cs 中的 AddDBContext 条目

builder.Services.AddDbContextFactory<PowerBlazorDbContext>(
opt => opt.UseSqlServer(
    builder.Configuration.GetConnectionString("PowerBlazorDb")));

然后我将注入更改为上下文工厂

public class PowerService : IPowerService
{

    private readonly IDbContextFactory<PowerBlazorDbContext> _contextFactory;

    public PowerService(IDbContextFactory<PowerBlazorDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

然后我使用 var _context = await _contextFactory.CreateDbContextAsnyc() 在从 dbcontext 的旧方法使用的每个 _context 实例前面添加了等待。

public async Task<ScheduledForward> ScheduleForward(MailboxUser forwardFrom, MailboxUser forwardTo,bool sendToBoth, DateTime startTime, DateTime stopTime)
    {
        DateTimeOffset start = new DateTimeOffset(startTime);
        DateTimeOffset stop = new DateTimeOffset(stopTime);
        var scheduled = new ScheduledForward()
        {
            FromDisplay = forwardFrom.DisplayName,
            FromId = forwardFrom.Identity,
            ToId = forwardTo.Identity,
            ToDisplay = forwardTo.DisplayName,
            SendToBoth = sendToBoth,
            StartTime = startTime,
            EndTime = stopTime,
            StartJobId = "",
            StopJobId = "",
        };
        
        scheduled.StartJobId = BackgroundJob.Schedule<PowerRun>(x => x.ForwardMailboxAsync(forwardFrom.Identity, forwardTo.Identity, sendToBoth), start);
        scheduled.StopJobId = BackgroundJob.Schedule<PowerRun>(x => x.RemoveForwardByScheduleAsync(scheduled), stop);
        await using var _context = await _contextFactory.CreateDbContextAsync();
        await _context.ScheduledForwards.AddAsync(scheduled);
        await _context.SaveChangesAsync();
        return scheduled;
    }
© www.soinside.com 2019 - 2024. All rights reserved.