データ リーダーが閉じられているときに 'Read' を呼び出す操作は無効です。

こんにちは。beaglesoftの真鍋です。

ASP.NET MVC5とEntityFrameworkを中心に開発をしていますが、開発中にやたらめったらと「データ リーダーが閉じられているときに 'Read' を呼び出す操作は無効です。」と例外が発生し、どうした物かと悩みました。

具体的な例外は以下の通りです。

System.Data.Entity.Core.EntityCommandExecutionException はユーザー コードによってハンドルされませんでした。
  HResult=-2146232004
  Message=データ リーダーが閉じられているときに 'Read' を呼び出す操作は無効です。
  Source=EntityFramework
  StackTrace:
       場所 System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.HandleReaderException(Exception e)
       場所 System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()
       場所 System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.RowNestedResultEnumerator.MoveNext()
       場所 System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.ObjectQueryNestedEnumerator.TryReadToNextElement()
       場所 System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.ObjectQueryNestedEnumerator.MoveNext()
       場所 System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
       場所 girafa.Repository.User.ApplicationUserRepository.FindById(String applicationUserId) 
場所 C:\Users...
  InnerException: 
       HResult=-2146233079
       Message=リーダーが閉じている場合は、Read の呼び出しは無効です。
       Source=System.Data
       StackTrace:
            場所 System.Data.SqlClient.SqlDataReader.TryReadInternal(Boolean setTimeout, Boolean& more)
            場所 System.Data.SqlClient.SqlDataReader.Read()
            場所 System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()
       InnerException: 

ずーと昔に位案サーバー型のアプリケーションを開発していた頃に1つのコネクションを使い回して閉じた後に開くと発生することが多かった事象のような気がします。

ただ、今回はDbContextUnityで以下の通り管理しているので大丈夫なはずと。

/// <summary>
/// Specifies the Unity configuration for the main container.
/// </summary>
public class UnityConfig
{
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    ...

    /// <summary>Registers the type mappings with the Unity container.</summary>
    /// <param name="container">The unity container to configure.</param>
    /// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to 
    /// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
    public static void RegisterTypes(IUnityContainer container)
    {
        ...
        container.RegisterType<BasicContext>(new PerRequestLifetimeManager(),
            new InjectionFactory(_ =>
            {
                var basicContext = new BasicContext();
                basicContext.Database.Log = s =>
                {
                    if (Logger.IsDebugEnabled)
                    {
                        Logger.Debug("SQL:{0}", s);
                    }
                };
                return basicContext;
            }));
        
        ...
    }
}

blog.beaglesoft.net

もちろん、DbContextPerRequestLifetimeManagerを利用してページ生成毎に生成と破棄を管理するようにしています。

ところが、UnityMvcActivator.csを確認してみたところ以下の設定がコメントインされていませんでした。

/// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
public static class UnityWebActivator
{
    /// <summary>Integrates Unity when the application starts.</summary>
    public static void Start() 
    {
        var container = UnityConfig.GetConfiguredContainer();

        FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
        FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
            
            ↓↓↓ ここをコメントインしないとPerRequestLifetimeManagerは有効にならない…
        // TODO: Uncomment if you want to use PerRequestLifetimeManager
        // Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
    }

    /// <summary>Disposes the Unity container when the application is shut down.</summary>
    public static void Shutdown()
    {
        var container = UnityConfig.GetConfiguredContainer();
        container.Dispose();
    }
}

ということで、以下をコメントインすることで接続まわりのエラーは発生しなくなりました。

Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

スペシャルサンクス

今回は以下のサイトにお世話になりました!ありがとうございます。

ASP.NET MVC 5 で DI する #aspnetjp - KatsuYuzuのブログ

ASP.NET MVC5実践プログラミング

ASP.NET MVC5実践プログラミング