Blazor WASM API控制器中Hangfire后台作业触发JsonSerializationException自引用循环异常求助
解决Hangfire序列化时的自引用循环异常
这个问题的核心在于Hangfire需要序列化你要执行的方法及其参数,但你直接传递了ApplicationDbContext实例——DbContext内部关联的实体(比如Role和User)存在双向导航属性,导致Json序列化时触发自引用循环;而且更重要的是,DbContext是请求生命周期内的对象,请求结束后可能被释放,直接传给后台任务会带来生命周期问题。
下面是具体的解决方案,分两种场景推荐:
方案1:创建独立的静态任务方法(快速解决)
把原来的DbContext扩展方法改成独立的静态方法,在任务内部创建新的DbContext实例,避免传递DbContext:
第一步:修改API端点代码
不再传递DbContext给Hangfire,只传任务必要的参数:
[HttpPost("CreateTask")] public async Task<IActionResult> CreateTask(TaskCreateViewModel taskCreate) { if (!ModelState.IsValid) return BadRequest(ModelState); var task = new Shared.Models.ApplicationTask { UserId = taskCreate.UserId, ApplicationId = taskCreate.ApplicationId, Status = Status.CREATED.ToString(), Parameters = taskCreate.Parameters, Remarks = JsonSerializer.Serialize(taskCreate.Remarks), GUID = Guid.NewGuid().ToString(), FinishedFiles = "" }; DbContext.Add(task); await DbContext.SaveChangesAsync(); // 改用异步方法更贴合ASP.NET Core实践 // 只传递任务所需的核心参数,移除DbContext BackgroundJob.Enqueue(() => TaskRunner.RunTask(task.GUID, taskCreate.Remarks, taskCreate.Parameters)); return Ok(ModelState); }
第二步:创建独立的任务运行类
public static class TaskRunner { public static async Task RunTask(string guid, Dictionary<FileType, string> remarks, string parameter) { // 在任务内部创建新的DbContext实例,配置和Program.cs中一致 var dbOptions = new DbContextOptionsBuilder<ApplicationDbContext>() .UseSqlServer("你的数据库连接字符串") // 替换成实际连接字符串 .Options; using (var dbContext = new ApplicationDbContext(dbOptions)) { var task = await dbContext.Tasks.FirstAsync(t => t.GUID == guid); task.Status = Status.ANALYSING.ToString(); task.Start = DateTime.Now; dbContext.Update(task); await dbContext.SaveChangesAsync(); var appId = await dbContext.Applications .Where(a => a.FacultyId == task.ApplicationId) .Select(x => x.Id) .FirstAsync(); var result = await Runner.Runner .Apps[appId] .Run(resources: remarks, parameters: parameter); task.Status = result.Status.ToString(); task.End = result.End; task.FinishedFiles = result.ResultsSimulation.ToList().Serialize(); dbContext.Update(task); await dbContext.SaveChangesAsync(); } } }
方案2:使用依赖注入(推荐,更符合ASP.NET Core最佳实践)
如果你的项目已经使用依赖注入,让Hangfire从DI容器中获取DbContext,避免手动创建实例:
第一步:配置Hangfire支持DI
在Program.cs中确保Hangfire已配置并添加DI支持:
// 注册Hangfire服务 builder.Services.AddHangfire(config => { config.UseSqlServerStorage("你的数据库连接字符串"); // 替换成实际存储配置 }); builder.Services.AddHangfireServer();
第二步:创建任务服务类(带DI)
// 定义接口,方便依赖注入和测试 public interface ITaskRunner { Task RunTask(string guid, Dictionary<FileType, string> remarks, string parameter); } public class TaskRunner : ITaskRunner { private readonly ApplicationDbContext _dbContext; // 通过构造函数注入DbContext public TaskRunner(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task RunTask(string guid, Dictionary<FileType, string> remarks, string parameter) { var task = await _dbContext.Tasks.FirstAsync(t => t.GUID == guid); task.Status = Status.ANALYSING.ToString(); task.Start = DateTime.Now; _dbContext.Update(task); await _dbContext.SaveChangesAsync(); var appId = await _dbContext.Applications .Where(a => a.FacultyId == task.ApplicationId) .Select(x => x.Id) .FirstAsync(); var result = await Runner.Runner .Apps[appId] .Run(resources: remarks, parameters: parameter); task.Status = result.Status.ToString(); task.End = result.End; task.FinishedFiles = result.ResultsSimulation.ToList().Serialize(); _dbContext.Update(task); await _dbContext.SaveChangesAsync(); } }
第三步:注册任务服务到DI容器
在Program.cs中添加:
builder.Services.AddScoped<ITaskRunner, TaskRunner>();
第四步:修改API端点,使用DI调用任务
private readonly ApplicationDbContext _dbContext; private readonly ITaskRunner _taskRunner; // 注入ITaskRunner public YourApiController(ApplicationDbContext dbContext, ITaskRunner taskRunner) { _dbContext = dbContext; _taskRunner = taskRunner; } [HttpPost("CreateTask")] public async Task<IActionResult> CreateTask(TaskCreateViewModel taskCreate) { if (!ModelState.IsValid) return BadRequest(ModelState); var task = new Shared.Models.ApplicationTask { UserId = taskCreate.UserId, ApplicationId = taskCreate.ApplicationId, Status = Status.CREATED.ToString(), Parameters = taskCreate.Parameters, Remarks = JsonSerializer.Serialize(taskCreate.Remarks), GUID = Guid.NewGuid().ToString(), FinishedFiles = "" }; _dbContext.Add(task); await _dbContext.SaveChangesAsync(); // 通过Hangfire的泛型Enqueue方法,从DI容器中获取ITaskRunner实例 BackgroundJob.Enqueue<ITaskRunner>(runner => runner.RunTask(task.GUID, taskCreate.Remarks, taskCreate.Parameters)); return Ok(ModelState); }
额外提示:避免实体自引用的辅助配置
如果你的实体双向导航属性确实需要保留,可以通过以下方式减少序列化问题:
- 在DbContext中关闭延迟加载,避免不必要的导航属性被加载:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseLazyLoadingProxies(false); }
- 配置Json序列化忽略循环引用(仅作为辅助,核心还是不要传递DbContext):
var jsonOptions = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles };
内容的提问来源于stack exchange,提问作者user11186117
相关产品推荐
相关产品推荐

