dotnet core在Task中使用依赖注入的Service/EFContext

C#:在Task中使用依赖注入的Service/EFContext

dotnet core时代,依赖注入基本已经成为标配了,这就不多说了.

前几天在做某个功能的时候遇到在Task中使用EF DbContext的问题,学艺不精的我被困扰了不短的一段时间,

于是有了这个文章.

先说一下代码结构和场景.

首先有一个HouseDbContext,代码大概是下面这样:

public class HouseDbContext : DbContext
{
    public HouseDbContext(DbContextOptions<HouseDbContext> options)
        : base(options)
    {
    }
    public DbSet<Notice> Notices { get; set; }
}

接着已经在StarUp.cs中初始化并注入了,注入代码是这样的:


services.AddDbContextPool<HouseDbContext>(options =>
{
    options.UseLoggerFactory(loggerFactory);
    options.UseMySql(Configuration["MySQLString"].ToString());
});

有一个NoticeService.cs 会用到HouseDbContext 进行增删查改

public class NoticeService
{

    private readonly HouseDbContext _dataContext;

    public NoticeService(HouseDbContext dataContext)
    {
        _dataContext = dataContext;
    }

    public Notice FindNotice(long id)
    {
        var notice = _dataContext.Notices.FirstOrDefault(n =>n.Id == id);
        return notice;
    }
}

当然我们也需要把NoticeService注入到容器中,类似这样


services.AddScoped<NoticeService,NoticeService>();

现在一切都是很美好的,也能正常查询出Notice

然后某一天来了,有个需求是Update Notice之后需要把Notice同步到另外一个地方,例如Elasticsearch?

代码如下:


public class NoticeService
{

    private readonly HouseDbContext _dataContext;

    public NoticeService(HouseDbContext dataContext)
    {
        _dataContext = dataContext;
    }

    public Notice FindNotice(long id)
    {
        var notice = _dataContext.Notices.FirstOrDefault(n =>n.Id == id);
        return notice;
    }

    public void Save(Notice notice)
    {
        _dataContext.Notices.Add(notice);
        _dataContext.SaveChanges();
        Task.Run(() =>
        {
            try
            {
                var one = _dataContext.Notices.FirstOrDefault(n =>n.Id == notice.Id);
                // write to other
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        });
    }
}

然后一跑...

代码炸了...

恭喜你获得跨线程使用EF DbContext导致上下文不同步的异常.

错误大概长这样.


System.ObjectDisposedException: 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: ‘HouseDbContext‘.

估计现在整个人都不好了.

这个撒意思呢?


无法访问被释放的对象。

这种错误的一个常见原因是使用从依赖注入中解决的上下文,然后在应用程序的其他地方尝试使用相同的上下文实例。

如果您在上下文上调用Dispose(),或者在using语句中包装上下文,可能会发生这种情况。如果使用依赖项注入,则应该让依赖项注入容器处理上下文实例。

用人话来说是什么意思呢?

这里的HouseDbContext是依赖注入进来的,生命周期由容器本身管理;

在Task.Run中再次使用HouseDbContext实例中由于已经切换了线程了,

HouseDbContext实例已经被释放掉了,无法再继续使用同一个实例,我们应该自己初始化HouseDbContext来用.

到这里的话,上次我做的时候心生一计:

既然我们不能直接从构造函数注入的HouseDbContext实例的话,我们是不是可以直接从依赖注入容器中拿一个实例回来呢?

那在dotnet core里面可以用个什么从容器中取出实例呢?

答案是:IServiceProvider

代码如下:


public class NoticeService
    {

        private readonly HouseDbContext _dataContext;

        private readonly IServiceProvider _serviceProvider;

        public NoticeService(HouseDbContext dataContext,IServiceProvider serviceProvider)
        {
            _dataContext = dataContext;
            _serviceProvider = serviceProvider;
        }

        public Notice FindNotice(long id)
        {
            var notice = _dataContext.Notices.FirstOrDefault(n => n.Id == id);
            Task.Run(() =>
            {
                try
                {
                    var context = _serviceProvider.GetService<HouseDbContext>();
                    var one = context.Notices.FirstOrDefault(n => n.Id == notice.Id);
                    Console.WriteLine(notice.Id);
                    // write to other
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            });
            return notice;
        }

        public void Save(Notice notice)
        {
            _dataContext.Notices.Add(notice);
            _dataContext.SaveChanges();
            Task.Run(() =>
            {
                try
                {
                    var one = _dataContext.Notices.FirstOrDefault(n => n.Id == notice.Id);
                    // write to other
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            });
        }
    }

跑一下看看...

然而事实告诉我,实例是能拿得到,然而还是会炸,错误是一样的.

原因其实还是一样的,这里已经不受依赖注入托管了,人家的上下文你别想用了.

那咋办呢...

在EF6,还可以直接new HouseDbContext 一个字符串进去初始化,在EF Core这里,已经不能这样玩了.

那可咋办呢?

翻了好多资料都没看到有人介绍过咋办,最后居然还是在官网教程里面找到了样例.

先看代码...

 Task.Run(() =>
{
    try
    {
        var optionsBuilder = new DbContextOptionsBuilder<HouseDbContext>();
        // appConfiguration.MySQLString appConfiguration是配置类,MySQLString为连接字符串
        optionsBuilder.UseMySql(appConfiguration.MySQLString);
        using (var context = new HouseDbContext(optionsBuilder.Options))
        {
            var one = context.Notices.FirstOrDefault(n => n.Id == notice.Id);
            // 当然你也可以直接初始化其他的Service
            var nService = new NoticeService(context,null);
            var one =nService.FindOne(notice.Id);
        }

    }catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
});

教程代码在:Configuring a DbContext

大功告成...

原文地址:https://www.cnblogs.com/liguobao/p/9992451.html

时间: 2024-10-04 21:50:18

dotnet core在Task中使用依赖注入的Service/EFContext的相关文章

ASP.NET Core 新建线程中使用依赖注入的问题

问题来自博问的一个提问 .net core 多线程数据保存的时候DbContext被释放 . TCPService 通过构造函数注入了 ContentService , ContentService 的实例依赖了 AppDbContext (继承自 EF Core 的 DbContext).在 TCPService 中通过 Thread.Start 启动了一个新的线程执行了 TCPService 中的 Receive 方法,在 Receive 方法中通过 ContentService 进行保存数

ASP.NET Core中的依赖注入(2):依赖注入(DI)

参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/project-layout.html http://www.yuanjiaocheng.net/ASPNET-CORE/projectjson.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-configuration.html http://www.yuanjiaocheng.net/ASPNET-CORE/core-middleware.htm

ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】

通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最终采用何种方式提供我们所需的服务实例取决于最终选择了怎样的ServiceCallSite,而服务注册是采用的ServiceDescriptor有决定了ServiceCallSite类型的选择.我们将众多不同类型的ServiceCallSite大体分成两组,一组用来创建最终的服务实例,另一类则与生命周

ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】

到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个ServiceProvider对象,何创建ServiceScope,以及如何提供一个服务实例的集合. 一.提供一个ServiceProvider对象 我们知道当将服务类型指定为IServiceProvider接口并调用ServiceProvider的GetService方法是,ServiceProvider对象本

ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】

本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还对这个依赖注入系统底层的实现原理具有好奇心,可以继续阅读这一节的内容. 目录一.ServiceCallSite 二.Service 三.ServiceEntry 四.ServiceTable 五.ServiceProvider 作为DI容器的体现,ServiceProvider是ASP.NET Co

深入理解net core中的依赖注入、Singleton、Scoped、Transient(四)

相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient(二) 深入理解net core中的依赖注入.Singleton.Scoped.Transient(三) 一.什么是依赖注入(Denpendency Injection) 这也是个老身常谈的问题,到底依赖注入是什么? 为什么要用它? 初学者特别容易对控制反转IOC(Iversion of Contr

深入理解net core中的依赖注入、Singleton、Scoped、Transient(一)

相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient(二) 深入理解net core中的依赖注入.Singleton.Scoped.Transient(三) 深入理解net core中的依赖注入.Singleton.Scoped.Transient(四) 在学习 Asp.Net Core 的过程中,注入可以说是无处不在,对于 .Net Core 来说

NET Core源代码通过Autofac实现依赖注入

查看.NET Core源代码通过Autofac实现依赖注入到Controller属性 阅读目录 一.前言 二.使用Autofac 三.最后 回到目录 一.前言 在之前的文章[ASP.NET Core 整合Autofac和Castle实现自动AOP拦截]中,我们讲过除了ASP.NETCore自带的IOC容器外,如何使用Autofac来接管IServiceProvider进行依赖注入. 最近老有想法在ASP.NET Mvc Core中实现Controller的属性值的依赖注入,但是找遍了Micros

SignalR中的依赖注入

什么是依赖注入? 如果你已经熟悉依赖注入可以跳过此节. 依赖注入 (DI) 模式下,对象并不为自身的依赖负责. 下边的例子是一个主动 DI. 假设你有个对象需要消息日志.你可能定义了一个日志接口: C# interface ILogger { void LogMessage(string message); } 在你的对象中,你可以创建一个 ILogger来记录消息. C# // 不用依赖注入. class SomeComponent { ILogger _logger = new FileLo