【问题标题】:.net transient database context being disposed prematurely.net 瞬态数据库上下文被过早处理
【发布时间】:2020-06-03 04:57:13
【问题描述】:

我正在将使用 EF6 的 asp.net mvc5 应用程序移动到使用 EF Core 的 asp.net core MVC 3.0。

在我的 mvc5 应用程序中,我有一些修改数据库的管理操作需要很长时间,所以当我创建一个与请求上下文无关的新 DBContext 然后运行任务时,我使用了一种模式在后台使用 Task.Run。这多年来一直运行良好。

在转换为 .net 核心时,不清楚如何以我在旧代码库中的方式创建新的 DBContext。看来我应该能够在这些情况下创建一个 Transient DBContext 并且一切都应该没问题。

所以我创建了一个名为 MyTransientDbContex 的 MyDbContext 子类,并在我的 Configure 类中添加了这个服务:

            services.AddDbContext<MyTransientDbContex>(options =>
                options.UseSqlServer(
                    context.Configuration.GetConnectionString("MyContextConnection")),
                ServiceLifetime.Transient, ServiceLifetime.Transient);

在我的控制器中,我将上下文注入到需要临时服务的操作中并生成一个线程来处理它:

public ActionResult Update([FromServices] MyTransientContext context) {

    Task.Run(() => 
    {
        try {
            // Do some long running operation with context
        }
        Catch (Exception e) {
            // Report Exception
        }
        finally {
            context.Dispose();
        }
    }

    return RedirectToAction("Status");
}

我不希望我的瞬态上下文在 finally 块之前被释放。但是在尝试访问后台线程上的上下文时出现此异常:

Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyTransientContext'.'

确实,上下文对象上的 _disposed 标志设置为 true。

我在 MyTransientContext 的构造函数上放置了一个断点,并为 this 指针“创建了一个对象 ID”,以便我可以跟踪该对象。正在创建此瞬态对象,并且与注入到我的控制器操作中的对象相同。它也是我在抛出异常时尝试引用的同一对象。

我尝试在 _disposed 成员上设置数据断点,以便在设置为 true 时启用调用堆栈,但断点不会绑定。

我还尝试覆盖 MyTransientContext 上的 Dispose 方法,直到我在 finally 块中显式 dispose 才调用它,这是在抛出并捕获异常之后。

我觉得我在这里遗漏了一些基本的东西。这不是临时服务的用途吗?什么会处理 Transient 服务?

最后一个细节 - MyTransientContext 派生自 MyContext,而 MyContext 又派生自 IdentityDbContext (Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContex)

编辑:我之所以选择使用 Transient 是因为这个 ef 核心文档页面:https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext。它指出“......任何显式并行执行多个线程的代码都应确保不会同时访问 DbContext 实例。使用依赖注入,这可以通过将上下文注册为作用域并创建作用域(使用 IServiceScopeFactory)来实现对于每个线程,或者通过将 DbContext 注册为瞬态(使用带有 ServiceLifetime 参数的 AddDbContext 的重载)。”

正如 xabikos 所指出的,这似乎被 asp.net DI 系统的范围覆盖,看起来该系统创建的任何内容都被限定为请求上下文,包括瞬态对象。有人可以指出记录在哪里,以便我更好地了解如何处理这些限制吗?

【问题讨论】:

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


    【解决方案1】:

    如果你想管理服务的生命周期,你可以手动实例化它(或使用工厂):

    public ActionResult Update()
    {
        Task.Run(() => 
        {
            using(var context = new MyTransientContext(...))
            {
                try 
                {
                    // Do some long running operation with context
                }
                catch (Exception e) 
                {
                    // Report Exception
                }
            }
        }
        return RedirectToAction("Status");
    }
    

    或者您可以使用 IServiceProvider 来获取和管理服务:

    public class MyController
    {
        private IServiceProvider _services;
    
        public MyController(IServiceProvider services)
        {
            _services = services;
        }
    
        public ActionResult Update()
        {
            var context = (MyTransientContext)_services.GetService(typeof(MyTransientContext));
            Task.Run(() =>
            {
                using (context)
                {
                    try
                    {
                        // Do some long running operation with context
                    }
                    catch (Exception e)
                    {
                        // Report Exception
                    }
                }
            }
            return RedirectToAction("Status");
        }
    }
    

    【讨论】:

    • 谢谢@Orwel - 是的,我要替换的基本上是工厂模式。问题是在我的 mvc5/ef6 解决方案中,很容易创建断开连接的 DbContext。我不确定如何在 .net 核心情况下做到这一点。我所看到的一切都假设一个人通过 DI 创建 DbContext。
    • 我想多了 - 使用此模式的唯一困难是将连接字符串获取到构造函数,在我的情况下,我可以从现有连接中挖掘连接字符串,因为我总是有可用的。但我想在其他情况下传递配置对象也可以。
    • 也许与 IServiceProvider?请参阅我的答案中的编辑。
    【解决方案2】:

    您混合了由 asp.net core 提供的内部 DI 容器创建的瞬态对象的概念。

    您在内部 DI 系统中将 MyTransientContext 配置为瞬态。这实际上意味着每次创建范围时都会返回一个新实例。对于 asp.net 应用程序,此范围匹配 HTTP 请求。当请求结束时,如果适用,所有对象都会被释放。

    现在在您的代码中,这是一个同步操作方法,您使用Task.Run 生成一个任务。这是一个异步操作,你不需要await。实际上,在执行期间,这将开始但不等待完成,重定向将发生并且请求将结束。此时如果你尝试使用注入的实例,你会得到异常。

    如果您想解决此问题,您需要更改为异步操作并在 Task.Run 上等待。而且很可能您不需要生成新的Task。但是您需要了解这可能不是最好的方法,因为它需要在重定向发生之前完成长时间的操作。

    对此的替代方法是使用消息传递机制,并发送触发此操作的消息。您还有另一个组件,例如 worker service,它会侦听这些消息并进行处理。

    【讨论】:

    • 谢谢@xabikos - 您的描述解释了为什么会发生这种情况。在我的上下文中,这两种解决方法都不实用。我认为从长远来看,我将研究工作人员服务,但我仍然需要弄清楚如何创建一个不受请求上下文范围限制的 DbContext 以使任何解决方案(除了使操作异步)工作。关于如何做到这一点的任何提示?
    猜你喜欢
    • 2019-08-21
    • 2010-10-26
    • 2017-05-18
    • 2015-03-18
    • 1970-01-01
    • 1970-01-01
    • 2018-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多