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