Entity Framework Core のクエリ処理時に System.InvalidOperationException: 'There is already an open DataReader associated with this Connection which must be closed first.' エラーが発生する - C#

Entity Framework Core のクエリ処理時に System.InvalidOperationException: 'There is already an open DataReader associated with this Connection which must be closed first.' エラーが発生する原因と対処法を紹介します。

概要

Entity Framework Coreでネストして検索を実行すると、以下の例外が発生します。
エラーメッセージ
System.InvalidOperationException: 'There is already an open DataReader associated with this Connection which must be closed first.'

現象の確認

再現するプログラムを作成します。

テーブル

以下のテーブルを用意します。
階層構造を表現するデータで、paernt の値が、上位のテーブルの親のIDになっています。
hst_level1
id label value
1 sound 音楽
2 image 画像
3 movie 動画
hst_level2
id parent label value
1 1 wav wave-sound
2 1 mp3 mp3-sound
3 1 flac flac-sound
4 2 jpeg jpeg-image
5 2 png png-image
6 3 mp4 mp4-movie
7 3 wmv wmv-movie
hst_level3
id parent label value
1 2 penguin.mp3 penguin-dance
2 2 duck.mp3 relux duck
3 3 bear.flac bear orchestra

以下の構造を表現しています。
  • sound : 音楽
    • wav : wave-sound
    • mp3 : mp3-sound
      • penguin.mp3 : penguin-dance
      • duck.mp3 : relux duck
    • flac : flac-sound
      • bear.flac : bear orchestra
  • image : 画像
    • jpeg : jpeg-image
    • png : png-image
  • movie : 動画
    • mp4 : mp4-movie
    • wmv : wmv-movie

UI

下図のフォームを作成します。ボタンと複数行のテキストボックスを配置します。

コード

以下のコードを記述します。
using EntityFrameworkCoreNest.Models;

namespace EntityFrameworkCoreNest
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      IPentecSandBoxContext cx = new IPentecSandBoxContext();
      IQueryable<HstLevel1> rec = cx.HstLevel1s;

      foreach (HstLevel1 r1 in rec) {
        IQueryable<HstLevel2> rec2 = cx.HstLevel2s.Where( i => i.Id==r1.Id);
        textBox1.Text += string.Format("{0} : {1}\r\n", r1.Label, r1.Value);

        foreach (HstLevel2 r2 in rec2) {
          IQueryable<HstLevel3> rec3 = cx.HstLevel3s.Where(j => j.Id == r2.Id);
          textBox1.Text += string.Format("\t{0} : {1}\r\n", r2.Label, r2.Value);

          foreach (HstLevel3 r3 in rec3) {
            textBox1.Text += string.Format("\t\t{0} : {1}\r\n", r3.Label, r3.Value);
          }
        }
      }
    }
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。[button1]をクリックします。


System.InvalidOperationException 例外が発生します。

原因

IQueryable<HstLevel1>でレコードを取得した場合、foreachのループ内で要素にアクセスした際には、DataReaderからの読み出されます。 DataReaderからの読み出しとなるため、DataReaderは開いた状態です。その状態で別のクエリを実行するとDataReaderを2重に開くことになり、 There is already an open DataReader associated with this Connection which must be closed first.の例外が発生します。

対処法

ループ中にDataReaderにアクセスしない動作にするために、ループ前にDataReaderを閉じる必要があります。 DataReader を閉じるには、DbSetやIQuerableオブジェクトをListオブジェクトに変換して、保持するコードに変更します。

以下のコードに変更します。
using EntityFrameworkCoreNest.Models;

namespace EntityFrameworkCoreNest
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      IPentecSandBoxContext cx = new IPentecSandBoxContext();
      List<HstLevel1> rec = cx.HstLevel1s.ToList();

      foreach (HstLevel1 r1 in rec) {
        List<HstLevel2> rec2 = cx.HstLevel2s.Where(i => i.Parent == r1.Id).ToList();
        textBox1.Text += string.Format("{0} : {1}\r\n", r1.Label, r1.Value);

        foreach (HstLevel2 r2 in rec2) {
          List<HstLevel3> rec3 = cx.HstLevel3s.Where(j => j.Parent == r2.Id).ToList();
          textBox1.Text += string.Format("\t{0} : {1}\r\n", r2.Label, r2.Value);

          foreach (HstLevel3 r3 in rec3) {
            textBox1.Text += string.Format("\t\t{0} : {1}\r\n", r3.Label, r3.Value);
          }
        }
      }
    }
  }
}

解説

ToList() メソッドを呼び出し、List<T> 型に変換することで、すべてのレコードを読み出し、foreachループ内でDataReaderにアクセスする動作でなくなります。
  List<HstLevel1> rec = cx.HstLevel1s.ToList();
  List<HstLevel2> rec2 = cx.HstLevel2s.Where(i => i.Id == r1.Id).ToList();
  List<HstLevel3> rec3 = cx.HstLevel3s.Where(j => j.Id == r2.Id).ToList();

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button2]をクリックします。例外は発生せず、レコードの取得ができました。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2023-06-18
作成日: 2023-06-17
iPentec all rights reserverd.