在 Core 中异步保存文件会导致 DbContext ASP.NET 异常



我有一个为客户保存文档信息的应用程序。我正在尝试添加功能以允许用户上传 PDF 并将其保存到磁盘。我正在使用此处和此处的推荐代码。

当代码工作时,一切看起来都很好 - 文档实体保存到数据库,上传的文件保存到磁盘。但是,我收到零星的异常 - 例如System.ObjectDisposedException ("Cannot access a disposed object"),有时DbUpdate异常说引用为空,等等......我可以重现该问题,但不是每次都 100%,也不是每次都有相同的例外。

我怀疑这是由于在保存文件时使用CopyToAsync引起的。我对异步代码不是很有经验,我可能做错了什么。如果我将方法更改为使用同步CopyTo,它似乎可以解决问题。我试图理解为什么会发生这种情况。

我正在使用AutoFac注入我的存储库和DbContext。下面是控制器调用的服务方法的代码:

public async Task<int> SaveDocument(DocumentDetailDto dto, string user, string webRoot)
{
documentToSave = new Document();
// Code not shown that maps dto to new document and adds it to context
if (dto.FileUpload != null && dto.FileUpload.Length > 0)
{
var fileName = $"{dto.CustomerId}-{dto.DocumentTypeId}-{DateTime.Now.Ticks}.pdf";
var filePath = Path.Combine(webRoot, "uploads");
using (var fileStream = new FileStream(Path.Combine(filePath, fileName), FileMode.Create))
{
await dto.FileUpload.CopyToAsync(fileStream);
}
}
_repo.Save(user);
return documentToSave.Id;
}

您认为此设置有什么明显的问题吗?在上下文中调用 save 之前,对 FileStream 使用异步时,我需要做什么特别的事情吗?我仍在尝试调试错误,但问题似乎是打开的文件流和尝试写入数据库的 DbContext 之间的某种冲突。欢迎任何尝试的想法!

编辑以添加一些附加代码:

以下是我在 Startup .cs 中注册 DbContext 的方式:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Add DbContext
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<DocContext>(
options => options.UseSqlServer(connection, b => b.MigrationsAssembly("InfrastructureLayer")));
// Add repository
services.AddScoped<IRepository, EntityFrameworkRepository<DocContext>>();
services.AddTransient<IResolveUserService, ResolveUserService>();
// Autofac setup
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<ServiceLayer.AutofacModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}

下面是存储库中的Save方法:

public virtual void Save(string user = "")
{
var modifiedEntries = Context.ChangeTracker.Entries<IEntity>()
.Where(x => x.State == EntityState.Modified)
.Select(x => x.Entity)
.ToList();
foreach (var entity in modifiedEntries)
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = user;
}
var newEntries = Context.ChangeTracker.Entries<IEntity>()
.Where(x => x.State == EntityState.Added)
.Select(x => x.Entity)
.ToList();
foreach (var entity in newEntries)
{
entity.CreatedDate = DateTime.UtcNow;
entity.CreatedBy = user;
}
Context.SaveChanges();
}

以下是从控制器调用SaveDocument方法的方式:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Save(DocumentDetailDto dto, [FromServices]IResolveUserService userService, [FromServices]IHostingEnvironment environment)
{
_service.SaveDocument(dto, userService.GetUser(), environment.WebRootPath);
return RedirectToAction("Detail", "Customers", new { id = dto.CustomerId });
}

谢谢!

您应该等待_service.SaveDocument完成。目前,您启动它并且不等待其结果。相反,您向用户发送成功的响应,请求被处理,使用的资源可能随时被处置。

将控制器操作更改为:

public async Task<IActionResult> Save(...
{
await _service.SaveDocument(...
return RedirectToAction(...
}

最新更新