1. 程式人生 > >dotnet core在Task中使用依賴註入的Service/EFContext

dotnet core在Task中使用依賴註入的Service/EFContext

exce pin 發生 回來 set enc ech 不能 結構

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

大功告成...

dotnet core在Task中使用依賴註入的Service/EFContext