技術日記
Entity SQLで「クエリの結果を複数回列挙することはできません。」エラーEdit

Entity SQLで取ってきたデータに対して、まずCount()で全件数を取得してから、ページング用の実データをSkip、Takeするような処理を書き、それをforeachで回そうとすると、「クエリの結果を複数回列挙することはできません。」エラーが出る。

擬似コードで言うところの、

[sourcecode language="c#"]
var db = new dbEntity();
var sql = "select * from table order by id";
var parameters = new List();
var result = db.ExecuteStoredQuery(sql, parameters);
int count = result.Count();
int page = 1;
int pagesize = 20;
int pagecount = (int)(count / pagesize) + 1;
foreach (var row in result.Skip( pagesize * (page - 1)).Take(pagesize))
{
  // ...
}
[/sourcecode]
なんて感じの処理。

必ず出るわけでなく、出たり出なかったりするのだが、どうやらその条件は「パラメタライズされたクエリーかどうか」らしい。上記みたいにparametersが空の場合は問題なく動く。動的にwhere条件を組み立てたりして、parametersにその条件値を入れていたりすると、foreachのところで「クエリの結果を複数回列挙することはできません。」が出る。

LINQ to SQLの仕組み(ほぼEntity SQLと同じ仕様だと思われる)がよくわかっていないんで、内部的にどのタイミングでどういうSQL文が発行されているのかがわからないんだけど、LINQ to SQLのドキュメントを斜め読みした限りでは、実際のSQL文の発行はできるだけ遅延実行するようになっているらしいから、Count()とかTake()とかforeachでEnumerableにしたタイミングで、実際にSQL文を発行しているのかな?

で、Count()とかで一度SQL文を実行して、パラメータ埋め込みを実行してしまったあとに、もう一度SQL文を発行しなければならない処理を呼び出すと、「クエリの結果を複数回列挙することはできません。」エラーが出ている? どうもすっきりしないけど、たぶんそんな感じのことが起こっているのではないかと推測。

ともかく、内部で何が起こっているのかよくわからないEntity SQLの機能を使うのはやめて、SQL文を組み立てるときに件数取得SQLと実体取得SQLの二つを生成しておいて、それぞれ呼ぶようにして回避。擬似コードで言うと、

[sourcecode language="c#"]
var countSql = "select count(*) as cnt from table";
var selectSql = "select * from table";
var parameters = new List();
var count = db.ExecuteStoredQuery(countSql, parameters).First();
var result = db.ExecuteStoredQuery(selectSql, parameters).Skip(offset).Take(limit);
[/sourcecode]
みたいな感じね。countみたいなデータを、モデルクラス以外の型でどうやって受け取ればいいのかには、しばらく悩んだけど、こんな感じでintで受けるといけるようだ。

Published At2011-11-24 20:42Updated At2011-11-24 20:42