处理完控制器后写入数据库

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 请求的线程。