きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × SIer

手探り入門×.NET Core×backgroundタスクでDBContextを使う

この記事の目的

この記事は、
バックグラウンド処理でも、インジェクションしたDBContextを使って処理をするための方法
をまとめることを目的としています。

本題

きっかけとなったエラー

実行したコード

//呼び出し元
public IActionResult Get()
        {
            Task.Run(()=>this._secondService.Execute());
            return Ok();
        }

//secondService
public void Execute()
{
            try
            {
                this._dotnetContext.TransactTest.Add(new TransactTest() //ここでエラー発生
                {
                    Id = int.Parse(DateTime.Now.ToString("hhmmss")),
                    Text = "Second Insert"
                });
                this._dotnetContext.SaveChanges();
            }
            catch (Exception ex)
            {
                throw ex;
            }
}

エラー内容:
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: 'dotnetContext'.
→DbContextがすでにDisposeされているから、アクセスできないよってこと

対処法1

DBContextをインジェクションしないで、Newする。
変更は、呼び出し元のサービス(ここでは、SecondService)に行う。

        public void Execute()
        {
            try
            {
                //ここを変更
                using (var context = new dotnetContext())
                {
                    context.TransactTest.Add(new TransactTest()
                    {
                        Id = int.Parse(DateTime.Now.ToString("hhmmss")),
                        Text = "Second Insert"
                    });
                    context.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

対処法2

IServiceScopeFactoryを使って、サービスの新しいインスタンスを取得する。
変更は、呼び出し元のサービス(というか、今回はコントローラー)

    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private ISecondService _secondService;
	//ServiceScopeFactoryをインジェクトするための設定を追加
        private IServiceScopeFactory _serviceScopeFactory;
        public WeatherForecastController(
                                         ISecondService secondService,
                                         IServiceScopeFactory serviceScopeFactory)
        {
            this._secondService = secondService;
    //ServiceScopeFactoryをインジェクト
            this._serviceScopeFactory = serviceScopeFactory;
        }

        [HttpGet]
        public IActionResult Get()
        {
            Task.Run(() =>
            {
                using (var scope = this._serviceScopeFactory.CreateScope())
                {
                    var newService = scope.ServiceProvider.GetService<ISecondService>();
		   //this._secondServiceを使うのではなく、新しく作成したサービスインスタンスを利用する。
                    newService.Execute();
                }
            });
            return Ok();
        }
    }

まとめ

対処1でも2でも期待通りの処理をすることができました。

違いとしては、
 1は、DBContextだけを作り直して何とかする、
 2は、Serviceごと新しく作り直す、
という点があげられると思います。

今回は、DBContextに関するエラーが発生したので、対処1で対応できます。が、しかし、ほかのオブジェクトに対しても同様なエラーが発生した場合、一々インスタンスを作成するのは面倒くさいですね。
あと、DBContextの接続文字列を編集する必要がある場合、対処1では、Contextを作成するタイミングで、接続文字列を設定しないといけないです。接続文字列は、おそらく設定ファイルにあるので、設定ファイルから設定を読み取る処理を入れないといけない・・・とかやっていくことになるのも面倒くさいですね。。

・・・どっちでもいいと思っていましたが、対処2を押していこうと思いました。