我正在使用 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 中看到的相同的错误。
好的,它似乎正在使用 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;
}