エンティティフレームワーク:このコマンドに関連付けられた開いているDataReaderが既に存在します


285

Entity Frameworkを使用していますが、このエラーが発生することがあります。

EntityCommandExecutionException
{"There is already an open DataReader associated with this Command which must be closed first."}
   at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...

手動の接続管理は行っていませんが。

このエラーは断続的に発生します。

エラーをトリガーするコード(読みやすくするために短縮):

        if (critera.FromDate > x) {
            t= _tEntitites.T.Where(predicate).ToList();
        }
        else {
            t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
        }

毎回新しい接続を開くためにDisposeパターンを使用します。

using (_tEntitites = new TEntities(GetEntityConnection())) {

    if (critera.FromDate > x) {
        t= _tEntitites.T.Where(predicate).ToList();
    }
    else {
        t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
    }

}

まだ問題がある

接続が既に開いている場合、EFはその接続を再利用しないのはなぜですか。


1
この質問は古くからあると思いますが、あなたpredicatehistoricPredicate変数のタイプを知りたいです。あなたがそれに渡しFunc<T, bool>た場合、Where()コンパイルされて動作する場合があることを発見しました(メモリ内の「場所」を実行するため)。あなたがしなけれならないことは、に渡すExpression<Func<T, bool>>ことWhere()です。
ジェームズ

回答:


351

接続を閉じることではありません。EFは接続を正しく管理します。この問題についての私の理解は、最初の接続が読み取りを完了する前に次のDataReaderが実行されている間に、単一の接続(または複数の選択を伴う単一のコマンド)で複数のデータ取得コマンドが実行されることです。例外を回避する唯一の方法は、複数のネストされたDataReadersを許可することです= MultipleActiveResultSetsをオンにします。これが常に発生する別のシナリオは、クエリの結果(IQueryable)を反復処理し、反復処理内で読み込まれたエンティティの遅延読み込みをトリガーする場合です。


2
それは理にかなっています。ただし、各メソッド内の選択は1つだけです。
ソニックソウル

1
@ソニック:それが問題です。実行されたコマンドが複数あるかもしれませんが、表示されません。これがプロファイラーで追跡できるかどうかはわかりません(2番目のリーダーが実行される前に例外がスローされる可能性があります)。クエリをObjectQueryにキャストして、ToTraceStringを呼び出し、SQLコマンドを確認することもできます。追跡するのは難しいです。私は常にMARSをオンにします。
Ladislav Mrnka、2011

2
@ソニック:実行および完了したSQLコマンドをチェックするつもりはありませんでした。
Ladislav Mrnka、2011

11
素晴らしい、私の問題は2番目のシナリオでした: 'クエリの結果(IQueryable)を反復処理し、反復処理内で読み込まれたエンティティの遅延読み込みをトリガーする場合です。
Amr Elgarhy、2009

6
MARSを有効にすることができ、明らかに悪い副作用を持っている:designlimbo.com/?p=235
セーレンBoisen

126

MARS(MultipleActiveResultSets)を使用する代わりに、複数の結果セットを開かないようにコードを記述できます。

あなたができることは、メモリにデータを取得することです。そうすれば、リーダーを開かなくて済みます。多くの場合、別の結果セットを開こうとしたときに結果セットを反復処理したことが原因です。

サンプルコード:

public class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogID { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostID { get; set; }
    public virtual Blog Blog { get; set; }
    public string Text { get; set; }
}

これらを含むデータベースを検索しているとしましょう:

var context = new MyContext();

//here we have one resultset
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5); 

foreach (var blog in largeBlogs) //we use the result set here
{
     //here we try to get another result set while we are still reading the above set.
    var postsWithImportantText = blog.Posts.Where(p=>p.Text.Contains("Important Text"));
}

次のように.ToList()を追加することで、これを簡単に解決できます。

var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();

これにより、entityframeworkは強制的にリストをメモリにロードするため、foreachループで繰り返し処理を行っても、リストを開くためにデータリーダーを使用しなくなり、代わりにメモリ内に存在します。

たとえば、一部のプロパティをレイジーロードする場合は、これは望ましくないかもしれません。これは主に、うまくいけばこの問題が発生する理由と理由を説明できる例なので、それに応じて決定を下すことができます


7
この解決策は私にとってうまくいきました。.ToList()をクエリの直後に追加し、結果に対して何かを行う前に追加します。
TJKjaer

9
これに注意し、常識を使用してください。あなたがいる場合はToList千個のオブジェクトをINGの、メモリにトンを増やすために起こっています。この特定の例では、内部クエリを最初のクエリと組み合わせると、2つではなく1つのクエリのみが生成されます。
kamranicus 2013年

4
@subkamran私の要点はまさにそれだけでなく、何かについて考え、状況に適したものを選択することでした。例は私が説明するために思いついたランダムなものです:)
Jim Wolff

3
間違いなく、私はコピー/ペーストハッピーな人々のためにそれを明示的に指摘したかっただけです:)
kamranicus

私を撃たないでください、しかしこれは決して質問に対する解決策ではありません。「メモリ内のデータのプル」はいつSQL関連の問題の解決策になるのですか?私はデータベースとおしゃべりするのが好きなので、「SQL例外がスローされるため」メモリに何かをプルすることは決してしたく​​ありません。それにもかかわらず、提供されたコードでは、データベースに2回連絡する理由はありません。1回の通話で簡単に行えます。このような投稿には注意してください。ToList、First、Single、...メモリでデータが必要な場合にのみ使用する必要があります(したがって、必要なデータのみ)。SQL例外が発生していない場合は使用しないでください。
Frederik Prijck

70

この問題を克服する別の方法があります。それがより良い方法であるかどうかは、あなたの状況に依存します。

この問題は遅延読み込みが原因で発生するため、これを回避する1つの方法は、Includeを使用して遅延読み込みを行わないことです。

var results = myContext.Customers
    .Include(x => x.Orders)
    .Include(x => x.Addresses)
    .Include(x => x.PaymentMethods);

適切なを使用すると、IncludeMARSの有効化を回避できます。ただし、1つを見落とすとエラーが発生するため、MARSを有効にするのがおそらく最も簡単な修正方法です。


1
魅力のように働いた。.IncludeMARSを有効にするよりもはるかに優れたソリューションであり、独自のSQLクエリコードを作成するよりもはるかに簡単です。
Nolonar 2014年

15
ラムダではなく.Include( "string")のみを書き込むことができるという問題がある場合は、拡張メソッドがそこにあるため、 "using System.Data.Entity"を追加する必要があります。
ジム・ウォルフ

46

反復しようとしているコレクションが遅延読み込みの一種(IQueriable)である場合、このエラーが発生します。

foreach (var user in _dbContext.Users)
{    
}

IQueriableコレクションを他の列挙可能なコレクションに変換すると、この問題が解決します。例

_dbContext.Users.ToList()

注:.ToList()は毎回新しいセットを作成するため、大きなデータを処理している場合はパフォーマンスの問題が発生する可能性があります。


1
最も簡単な解決策!Big UP;)
Jacob Sobus

1
無制限のリストを取得すると、パフォーマンスに重大な問題が発生する可能性があります。どうすればそれを賛成できますか?
SandRock、2016

1
@SandRockは、小さな会社で働いている人には適していません- SELECT COUNT(*) FROM Users= 5
Simon_Weaver 2017

5
それについてよく考えてください。このQ / Aを読んでいる若い開発者は、これが絶対に当てはまらない場合でも、これがオールタイムソリューションであると考えるかもしれません。回答を編集して、dbから無制限のリストをフェッチする危険性について読者に警告することをお勧めします。
サンドロック

1
@SandRockこれは、ベストプラクティスを説明する回答または記事をリンクするのに適した場所だと思います。
シンジャイ

13

コンストラクターにオプションを追加することで、問題を簡単に(プラグマティックに)解決しました。したがって、私は必要なときにのみそれを使用します。

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...

2
ありがとうございました。それは働いています。MultipleActiveResultSets = trueをweb.configの接続文字列に直接追加したところです
Mosharaf Hossain

11

接続文字列を設定してみてくださいMultipleActiveResultSets=true。これにより、データベースでのマルチタスクが可能になります。

Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework

それは私にとってはうまくいきます... app.configでの接続かプログラムでそれを設定したかに関係なく...これが役に立てば幸いです


接続文字列にMultipleActiveResultSets = trueを追加すると、問題が解決する可能性があります。これは反対投票されるべきではありませんでした。
アーロンヒュードン16

はい、接続文字列に追加する方法を実際に示しました
Mohamed Hocine '30

4

私はもともと、APIクラスの静的フィールドを使用してMyDataContextオブジェクトのインスタンスを参照することを決めていました(MyDataContextはEF5 Contextオブジェクトです)が、それが問題を引き起こしているようです。APIメソッドのすべてに次のようなコードを追加して、問題を修正しました。

using(MyDBContext db = new MyDBContext())
{
    //Do some linq queries
}

他の人々が述べたように、EFデータコンテキストオブジェクトはスレッドセーフではありません。したがって、静的オブジェクトに配置すると、最終的には適切な条件下で「データリーダー」エラーが発生します。

私の当初の想定では、オブジェクトのインスタンスを1つだけ作成する方が効率的で、メモリ管理が向上するというものでした。私がこの問題を研究して集めたものから、それはそうではありません。実際、APIへの各呼び出しを分離されたスレッドセーフイベントとして処理する方が効率的です。オブジェクトがスコープ外になったときに、すべてのリソースが適切に解放されるようにします。

これは、APIをWebServiceまたはREST APIとして公開するという次の自然な進行に進む場合に特に意味があります。

開示

  • OS:Windows Server 2012
  • .NET:インストール済み4.5、4.0を使用するプロジェクト
  • データソース:MySQL
  • アプリケーションフレームワーク:MVC3
  • 認証:フォーム

3

このエラーは、IQueriableをビューに送信して二重のforeachで使用すると発生することに気付きました。内部のforeachでも接続を使用する必要があります。簡単な例(ViewBag.parentsはIQueriableまたはDbSetにすることができます):

foreach (var parent in ViewBag.parents)
{
    foreach (var child in parent.childs)
    {

    }
}

簡単な解決策は.ToList()、コレクションを使用する前に使用することです。MARSはMySQLでは機能しないことにも注意してください。


ありがとうございました!ここのすべてが「ネストされたループが問題である」と述べましたが、誰もそれを修正する方法を述べていませんでした。ToList()DBからコレクションを取得するための最初の呼び出しをオンにしました。次にforeach、そのリストでa を実行しました。その後の呼び出しは、エラーを表示する代わりに完全に機能しました。
AlbatrossCafe

@AlbatrossCafe ...しかし、その場合、データがメモリにロードされ、クエリがDBではなくメモリ内で実行されることについて誰も言及していません
Lightning3

3

同じエラーが発生することがわかりました。あなたのFunc<TEntity, bool>にの代わりにを使用しているときに発生しました。Expression<Func<TEntity, bool>>predicate

すべてを変更するFunc'sExpression's、例外がスローされなくなりました。

私は信じているEntityFramworkといくつかの巧妙な物事んExpression's、それは単にで何しませんのFunc's


これにはもっと賛成票が必要です。私のDataContextクラスでメソッドを作成し(MyTParent model, Func<MyTChildren, bool> func)て、ViewModelsがwhereGeneric DataContextメソッドに特定の句を指定できるようにしました。これを行うまで何も機能しませんでした。
ジャスティン

3

この問題を軽減する2つのソリューション:

  1. .ToList()クエリの後で遅延読み込みを維持しながらメモリキャッシングを強制します。これにより、新しいDataReaderを開いて反復できます。
  2. .Include(/ クエリにロードする追加のエンティティ /)これはeager loadingと呼ばれ、DataReaderを使用したクエリの実行中に関連するオブジェクト(エンティティ)を(実際に)含めることができます。

2

MARSの有効化と結果セット全体のメモリへの取得の適切な中間点は、最初のクエリでIDのみを取得し、各エンティティを具体化するIDをループすることです。

例(この回答のように「ブログと投稿」サンプルエンティティを使用):

using (var context = new BlogContext())
{
    // Get the IDs of all the items to loop through. This is
    // materialized so that the data reader is closed by the
    // time we're looping through the list.
    var blogIds = context.Blogs.Select(blog => blog.Id).ToList();

    // This query represents all our items in their full glory,
    // but, items are only materialized one at a time as we
    // loop through them.
    var blogs =
        blogIds.Select(id => context.Blogs.First(blog => blog.Id == id));

    foreach (var blog in blogs)
    {
        this.DoSomethingWith(blog.Posts);

        context.SaveChanges();
    }
}

これを行うと、数千のオブジェクトグラフ全体ではなく、数千の整数のみがメモリに読み込まれます。これにより、MARSを有効にせずにアイテムごとに作業できるようにしながら、メモリ使用量を最小限に抑えることができます。

サンプルに見られるように、これのもう1つの優れた利点は、各項目をループするときに変更を保存できることです。ループの終了まで待つ必要はありません(または他のそのような回避策)。 MARSが有効になっています(ここここを参照)。


context.SaveChanges();ループの内側:(これは良いではありません、それはループの外でなければなりません。。。
Jawandシン

1

私の場合、myContext.SaveChangesAsync()呼び出しの前に「await」ステートメントが欠落していることがわかりました。これらの非同期呼び出しがデータリーダーの問題を修正する前に、待機を追加しました。


0

条件の一部をFunc <>または拡張メソッドにグループ化しようとすると、このエラーが発生します。次のようなコードがあるとします。

public static Func<PriceList, bool> IsCurrent()
{
  return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
              (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

Or

public static IEnumerable<PriceList> IsCurrent(this IEnumerable<PriceList> prices) { .... }

これをWhere()で使用しようとすると、例外がスローされます。代わりに、次のような述語を作成する必要があります。

public static Expression<Func<PriceList, bool>> IsCurrent()
{
    return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
                (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

さらに読むことができます:http//www.albahari.com/nutshell/predicatebuilder.aspx


0

この問題は、データをリストに変換するだけで解決できます

 var details = _webcontext.products.ToList();


            if (details != null)
            {
                Parallel.ForEach(details, x =>
                {
                    Products obj = new Products();
                    obj.slno = x.slno;
                    obj.ProductName = x.ProductName;
                    obj.Price = Convert.ToInt32(x.Price);
                    li.Add(obj);

                });
                return li;
            }

ToList()が呼び出しを行いますが、上記のコードは接続を破棄しません。したがって、あなたの_webcontextは、1行目で閉じられるリスクがあります
Sonic Soul

0

私の状況では、依存性注入の登録が原因で問題が発生しました。dbcontextを使用していたリクエストごとのスコープサービスをシングルトン登録済みサービスに注入していました。そのため、dbcontextが複数の要求内で使用されたため、エラーが発生しました。


0

私の場合、問題はMARS接続文字列とは関係なく、jsonシリアル化に関係していました。プロジェクトをNetCore2から3にアップグレードした後、このエラーが発生しました。

詳細については、こちらをご覧ください


-6

2番目のクエリの前に、次のコードセクションを使用してこの問題を解決しました。

 ...first query
 while (_dbContext.Connection.State != System.Data.ConnectionState.Closed)
 {
     System.Threading.Thread.Sleep(500);
 }
 ...second query

ミリ秒単位で睡眠時間を変更できます

PDスレッドを使用するときに便利


13
ソリューションにThread.Sleepを任意に追加することは悪い習慣です。ある値の状態が完全に理解されていない別の問題を回避するために使用される場合は特に悪いです。応答の下部に記載されている「スレッドを使用する」とは、少なくともスレッドの基本的な理解があることを意味すると考えていたでしょう。 Thread.Sleepを使用する方法-UIスレッドなど。
マイクツアー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.