【问题标题】:DbContext Depedency Injection Issue - System.ObjectDisposedException: Cannot access a disposed objectDbContext 依赖注入问题 - System.ObjectDisposedException:无法访问已处置的对象
【发布时间】:2019-05-17 14:28:05
【问题描述】:

我在 Ubuntu 机器上使用 MVC 框架编写了我的第一个 .Net 核心程序。在程序中,我试图与 SQLite 数据库进行交互。当通过控制器类进行处理时,数据库 CRUD 操作可以正常工作。但是,当我尝试对控制器外部的数据库进行操作时,出现以下错误

"未处理的异常:System.ObjectDisposedException: 无法访问已释放的对象。此错误的常见原因是释放从依赖注入中解析的上下文然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。如果您使用依赖注入,则应该让依赖注入容器负责处理上下文实例。 对象名称: 'MyDbContext'。"

对于控制器类之外的操作,我创建了一个名为MyDbWatch.cs的类(在项目根目录中)

    public interface IMyDbWatch { }

    public class MyDbWatch : IMyDbWatch
    {  
        private readonly MyDbContext _dbContext; 
        private static Timer _timer;
        private AutoResetEvent _autoEvent = null;

        public MyDbWatch(MyDbContext context)
        {
            _dbContext = context;   

            _autoEvent = new AutoResetEvent(false);
            _timer = new Timer(
                callback: async s => await OnTimerEventAsync(s),
                state: _autoEvent,
                dueTime: 5000,  
                period: 10000);              
        }    

        public async Task OnTimerEventAsync(Object stateInfo)    
        {    
            Console.WriteLine("retreiving from db - 1");
            var ienStates = from m in _dbContext.IenState select m;  
            Console.WriteLine("retreiving from db - 2");         
            var listdb = await ienStates.ToListAsync();
            Console.WriteLine("retreiving from db - 3");                             
        } 
    }

这里我如何在 Startup.cs 文件中注入不同的依赖项

public class Startup
{
    private MyDbWatch _myDbWatch;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {                
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });            

        services.AddDbContext<IpointWebMcmContext>(options =>
            options.UseSqlite(Configuration.GetConnectionString("IpointContext")));           

        services.AddScoped<IMyDbWatch, MyDbWatch>(); 

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
    IMyDbWatch dbwatch)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");                
            app.UseHsts();
        }

        _myDbWatch = dbwatch;           

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();           

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

MyDbWatch.cs 中的定时器回调函数 OnTimerEventAsync 首次被调用,并且调试文本“rereiving from db - 1”被打印在控制台中。之后我得到错误

未处理的异常:System.ObjectDisposedException:无法访问已处置的对象...

我们将不胜感激任何解决此问题的帮助。我需要数据库上的这种手表,通过使用 SignalR hub 框架(尚未包含在代码中)将数据推送到客户端。

【问题讨论】:

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


    【解决方案1】:

    这就是为什么应该避免静态。几乎每次你有这样的静态时,一些开发人员都会绊倒它,因为他们没有考虑事情实际上是如何工作的。

    static 关键字并不神奇。您有一个范围服务,您希望在其中保留状态(您的计时器),因此您只需在其上打一个 static 并收工。但是,此服务使用其他范围服务(您的上下文),这些服务现在与此静态计时器不同步,即计时器保持不变,但上下文没有。

    首先,如果您需要在应用程序生命周期内维护状态,您应该使用单例范围。这使您摆脱了static 的恐惧。但是,您需要利用服务器定位器模式来获取您的上下文,因为您无法将作用域实例注入到单例中。

    public class MyDbWatch : IMyDbWatch
    {  
        private readonly IServiceProvider _serviceProvider; 
        private readonly Timer _timer;
        private AutoResetEvent _autoEvent = null;
    
        public MyDbWatch(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
    
            _autoEvent = new AutoResetEvent(false);
            _timer = new Timer(
                callback: async s => await OnTimerEventAsync(s),
                state: _autoEvent,
                dueTime: 5000,  
                period: 10000);              
        }    
    
        public async Task OnTimerEventAsync(Object stateInfo)    
        {    
            using (var scope = _serviceProvider.CreateScope())
            {
                var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
    
                Console.WriteLine("retreiving from db - 1");
                var ienStates = from m in context.IenState select m;  
                Console.WriteLine("retreiving from db - 2");         
                var listdb = await ienStates.ToListAsync();
                Console.WriteLine("retreiving from db - 3");                             
             }
         } 
    }
    

    然后,在ConfigureServices

    services.AddSingleton<IMyDbWatch, MyDbWatch>(); 
    

    现在,我不知道您实际上想用这些来完成什么,因为您的代码没有多大意义,但以上是您能够安全地做到这一点的唯一方法.

    【讨论】:

    • 谢谢,您解决了这个问题。我做我正在做的事情是为了学习。我想使用 SignalR Core Hub 定期将数据推送到客户端。基于您的回答,我得到了一个集线器实例var hubContext = scope.ServiceProvider.GetRequiredService&lt;IHubContext&lt;MyHub&gt;&gt;();,然后发送消息await hubContext.Clients.All.SendAsync("DbStateMsg", listdb);。这一切都有效。但我在解析 JavaScript 中心客户端中的消息时遇到问题。
    • @Jatala 我建议您接受解决方案作为关闭此线程的答案,并打开一个新问题。
    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-28
    • 1970-01-01
    • 1970-01-01
    • 2019-11-20
    相关资源
    最近更新 更多