【问题标题】:Write to DB after Controller has been disposed在控制器被释放后写入数据库
【发布时间】:2016-06-08 08:59:07
【问题描述】:

情况

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

如果服务器响应缓慢、停机或过载,发送电子邮件可能需要很长时间。 E-Mail 发送者应该根据从 E-Mail 服务器接收到的状态更新数据库,例如当永久失败发生时,例如如果地址不存在,则将好友请求设置为“错误”状态。为此,E-Mail 组件实现了函数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
    }
}

【问题讨论】:

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


    【解决方案1】:

    首先,当您将 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 请求的线程。

    【讨论】:

    • 这种工作。问题是我无法将ApplicationDbContext 更改为从 DbContext 继承或站点由于某种原因中断。我最终这样做了:创建一个AsyncDbContext,它的内容基本上与ApplicationDbContext 相同,但继承自DbContext。然后我把我的membercontroller的构造函数改成了:public MembersController(DbContextOptions&lt;ApplicationDbContext&gt; options, ApplicationDbContext context),因为还是不能手动实例化AppicationDbContext。这样我就得到了。请求的上下文和异步上下文的选项对象。
    • 你对 ApplicationDbContext 的定义继承了什么?
    • ef 将其创建为public class ApplicationDbContext : IdentityDbContext&lt;ApplicationUser&gt;
    • 哦。这实际上意味着您正在使用 ASP.NET Identity,它是 EF Core 之上的一层。在此处查看此代码:github.com/aspnet/Identity/blob/dev/src/…
    【解决方案2】:

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

    【讨论】:

    • 我试过了,没用。我认为实体框架明确地将其与控制器一起处理。
    • 哇,这很有趣。在控制器内部,您能否创建一个控制器一无所知的新 dbContext 对象并将其传递给 userArgs?也许这会奏效?
    • 不,我不能。如您所见,构造函数接收ApplicationDbContext 类型的对象。当我尝试使用new ApplicationDbContext() 创建我自己的时,它会被创建,但是一旦您想从数据库读取或写入,您就会收到此错误:未配置数据库提供程序。在设置服务时,通过在 DbContext 类或 AddDbContext 方法中重写 OnConfiguring 来配置数据库提供程序。 注意: ApplicationDbContext 是由实体框架本身创建的类型,只有一个空的构造函数
    • 在这个问题 stackoverflow.com/questions/36609339/… 的注释中,它解释了您看到的问题:ASP.NET 中的 DbContexts 默认注册为范围,这意味着它们的生命周期是要求。当请求结束时,它们会被处理掉。建议的解决方案是获取一个在 DI 容器中注册的 DbContext,该容器具有更长的生命周期(即应用程序生命周期)。也许这会为您指明正确的方向。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-02
    • 2013-03-09
    • 2017-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多