在处理完Controller后写入DB

时间:2016-06-07 12:04:23

标签: asp.net-core-mvc entity-framework-core

情况

我们有一个控制器,用户可以在其中提交任意数量的电子邮件地址,以邀请其他(潜在)成员作为朋友。如果在数据库中找不到地址,我们会向该用户发送电子邮件消息。由于用户不必等待此过程完成以便继续工作,因此这是异步完成的。

如果服务器响应缓慢,关闭或过载,发送电子邮件可能需要很长时间。电子邮件发件人应根据从电子邮件服务器收到的状态更新数据库,例如,当发生永久性故障时,例如如果地址不存在,将朋友请求设置为“错误”状态。为此,电子邮件组件实现了函数SendImmediateAsync(From,To,Subject,Content,Callback,UserArg)。消息传递(或失败)后,将使用有关传递状态的某些参数调用回调。

当它最终调用委托时,DbContext对象已经被释放(因为控制器也是如此)并且我无法使用new ApplicationDbContext()手动创建新对象,因为没有构造函数接受连接字符串。

问题

如何在处理完控制器后长时间写入数据库?我还没想出如何为自己手动创建一个DbContext对象。类型ApplicationDbContext的对象被传递给Controller的构造函数,我希望我可以为自己实例化一个,但构造函数没有我可以提供的参数(例如连接字符串)。我想避免手动创建SQL连接并手动组装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
    }
}

2 个答案:

答案 0 :(得分:0)

为什么不直接将dbContext作为userArgs传递给SendImmediateAsync?然后dbContext将不会被释放,并且可以在您进行回调时传回。我很确定这应该有用。

答案 1 :(得分:0)

首先,当您使用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”类正确地使用并发来运行委托,因此它不会阻止处理HTTP请求的线程。