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

大功告成...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM