处理完控制器后写入数据库
Write to DB after Controller has been disposed
情况
我们有一个控制器,用户可以在其中提交任意数量的电子邮件地址以邀请其他(潜在)成员成为朋友。如果在数据库中找不到地址,我们会向该用户发送电子邮件。由于用户不必等待此过程完成才能继续工作,因此这是异步完成的。
如果服务器响应缓慢、关闭或过载,发送电子邮件可能需要很长时间。电子邮件发件人应根据从电子邮件服务器收到的状态更新数据库,例如,当永久性故障发生时(例如地址不存在),将好友请求设置为“错误”状态。为此,电子邮件组件实现了函数 SendImmediateAsync(From,To,Subject,Content,Callback,UserArg)
。传递消息(或传递失败)后,将使用有关传递状态的某些参数调用回调。
当它最终调用委托时,DbContext 对象已经被处理掉(因为控制器也被处理掉了),我无法使用 new ApplicationDbContext()
手动创建一个新对象,因为没有接受连接的构造函数字符串.
问题
如何在处理完控制器后很长时间才写入数据库?我还没有想出如何为自己手动创建一个 DbContext
对象。 ApplicationDbContext
类型的对象被传递给控制器的构造函数,我希望我可以为自己实例化一个,但构造函数没有我可以提供的参数(例如连接字符串)。我想避免手动创建 SQL Connection 和 assemble INSERT 语句,并且更愿意使用我们已经设置的实体模型。
代码
该代码仅显示受影响的段,没有任何可读性错误检查。
[Authorize]
public class MembersController : Controller
{
private ApplicationDbContext _context;
public MembersController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT,
delegate (Guid G, object any)
{
//THIS IS NOT WORKING BECAUSE _context IS DISPOSED
var ctx = _context;
Guid Result = (Guid)any; //user supplied argument
if (G != Guid.Empty)
{
ctx.MailConfirmation.Add(new MailConfirmation()
{
EntryId = Result,
For = EntryFor.FriendRequest,
Id = G
});
if (G == MailHandler.ErrorGuid)
{
var frq = _context.FriendRequest.SingleOrDefault(m => m.Id == Result);
frq.Status = FriendStatus.Error;
ctx.Update(frq);
}
ctx.SaveChanges();
}
}, req.Id);
//rendering view
}
}
为什么不将 dbContext 作为 userArgs 传递给您的 SendImmediateAsync?然后 dbContext 将不会被处理,并且可以在您执行回调时传回。我很确定这应该有效。
首先,当您将 EF Core 与 ASP.NET Core 的依赖项注入一起使用时,每个 DbContext 实例都按请求限定范围,除非您在“.AddDbContext”中另有指定。这意味着您不应在该 HTTP 请求完成后尝试重新使用 DbContext 的实例。参见 https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options
另一方面,DbContextOptions 是单例,可以跨请求重复使用。
如果您需要关闭 HTTP 请求并在之后执行操作,您将需要创建一个新的 DbContext 范围并管理它的生命周期。
其次,你可以重载DbContext的基础构造函数,直接传入DbContextOptions。参见 https://docs.efproject.net/en/latest/miscellaneous/configuring-dbcontext.html
总的来说,这就是解决方案的样子。
public class MembersController : Controller
{
private DbContextOptions<ApplicationDbContext> _options;
public MembersController(DbContextOptions<ApplicationDbContext> options)
{
_options = options;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, CreateDelegate(_options) req.Id);
}
private static Action<Guid, object> CreateDelegate(DbContextOptions<ApplicationDbContext> options)
{
return (G, any) =>
{
using (var context = new ApplicationDbContext(options))
{
//do work
context.SaveChanges();
}
};
}
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) { }
// the rest of your stuff
}
当然,这假设您的 "MailHandler" class 正确地使用并发到 运行 委托,因此它不会阻止处理 HTTP 请求的线程。
情况
我们有一个控制器,用户可以在其中提交任意数量的电子邮件地址以邀请其他(潜在)成员成为朋友。如果在数据库中找不到地址,我们会向该用户发送电子邮件。由于用户不必等待此过程完成才能继续工作,因此这是异步完成的。
如果服务器响应缓慢、关闭或过载,发送电子邮件可能需要很长时间。电子邮件发件人应根据从电子邮件服务器收到的状态更新数据库,例如,当永久性故障发生时(例如地址不存在),将好友请求设置为“错误”状态。为此,电子邮件组件实现了函数 SendImmediateAsync(From,To,Subject,Content,Callback,UserArg)
。传递消息(或传递失败)后,将使用有关传递状态的某些参数调用回调。
当它最终调用委托时,DbContext 对象已经被处理掉(因为控制器也被处理掉了),我无法使用 new ApplicationDbContext()
手动创建一个新对象,因为没有接受连接的构造函数字符串.
问题
如何在处理完控制器后很长时间才写入数据库?我还没有想出如何为自己手动创建一个 DbContext
对象。 ApplicationDbContext
类型的对象被传递给控制器的构造函数,我希望我可以为自己实例化一个,但构造函数没有我可以提供的参数(例如连接字符串)。我想避免手动创建 SQL Connection 和 assemble INSERT 语句,并且更愿意使用我们已经设置的实体模型。
代码
该代码仅显示受影响的段,没有任何可读性错误检查。
[Authorize]
public class MembersController : Controller
{
private ApplicationDbContext _context;
public MembersController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT,
delegate (Guid G, object any)
{
//THIS IS NOT WORKING BECAUSE _context IS DISPOSED
var ctx = _context;
Guid Result = (Guid)any; //user supplied argument
if (G != Guid.Empty)
{
ctx.MailConfirmation.Add(new MailConfirmation()
{
EntryId = Result,
For = EntryFor.FriendRequest,
Id = G
});
if (G == MailHandler.ErrorGuid)
{
var frq = _context.FriendRequest.SingleOrDefault(m => m.Id == Result);
frq.Status = FriendStatus.Error;
ctx.Update(frq);
}
ctx.SaveChanges();
}
}, req.Id);
//rendering view
}
}
为什么不将 dbContext 作为 userArgs 传递给您的 SendImmediateAsync?然后 dbContext 将不会被处理,并且可以在您执行回调时传回。我很确定这应该有效。
首先,当您将 EF Core 与 ASP.NET Core 的依赖项注入一起使用时,每个 DbContext 实例都按请求限定范围,除非您在“.AddDbContext”中另有指定。这意味着您不应在该 HTTP 请求完成后尝试重新使用 DbContext 的实例。参见 https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options
另一方面,DbContextOptions 是单例,可以跨请求重复使用。
如果您需要关闭 HTTP 请求并在之后执行操作,您将需要创建一个新的 DbContext 范围并管理它的生命周期。
其次,你可以重载DbContext的基础构造函数,直接传入DbContextOptions。参见 https://docs.efproject.net/en/latest/miscellaneous/configuring-dbcontext.html
总的来说,这就是解决方案的样子。
public class MembersController : Controller
{
private DbContextOptions<ApplicationDbContext> _options;
public MembersController(DbContextOptions<ApplicationDbContext> options)
{
_options = options;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, CreateDelegate(_options) req.Id);
}
private static Action<Guid, object> CreateDelegate(DbContextOptions<ApplicationDbContext> options)
{
return (G, any) =>
{
using (var context = new ApplicationDbContext(options))
{
//do work
context.SaveChanges();
}
};
}
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) { }
// the rest of your stuff
}
当然,这假设您的 "MailHandler" class 正确地使用并发到 运行 委托,因此它不会阻止处理 HTTP 请求的线程。