web-dev-qa-db-ja.com

Entity Framework:このコマンドに関連したオープンなDataReaderがすでにあります

私は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はその接続を再利用しません。

272
Sonic Soul

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

337
Ladislav Mrnka

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ループ内でそれを繰り返すと、リストを開くためにデータリーダーを使用しなくなり、代わりにメモリ内になります。

たとえば、いくつかのプロパティを遅延ロードしたい場合、これは望ましくない場合があることを私は認識しています。これは主にあなたがこの問題に遭遇する可能性がある方法/理由をうまく説明している例ですので、あなたはそれに応じて決定を下すことができます

119
Jim Wolff

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

この問題は遅延ロードが原因で発生するので、回避する1つの方法は、Includeを使用して遅延ロードを行わないことです。

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

適切なIncludesを使用すると、MARSを有効にすることを回避できます。しかし、見逃した場合はエラーになるので、MARSを有効にするのがおそらく最も簡単な方法です。

65
Ryan Lundy

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

foreach (var user in _dbContext.Users)
{    
}

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

_dbContext.Users.ToList()

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

40

私は、コンストラクタにオプションを追加することで問題を簡単に(実用的に)解決しました。したがって、私は必要なときだけそれを使います。

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

接続文字列で "MultipleActiveResultSets = true"を設定してみてください。これにより、データベースでマルチタスクが可能になります。 "サーバー= yourserver; AttachDbFilename =データベース;ユーザーID = sa;パスワード= blah; MultipleActiveResultSets = true; App = EntityFramework"これは私のためにはたらきます参考になった

5
Mohamed Hocine

私はもともと私のAPIクラスで静的フィールドを使ってMyDataContextオブジェクトのインスタンス(MyDataContextはEF 5コンテキストオブジェクト)を参照することにしましたが、それが問題の原因と思われます。私はすべてのAPIメソッドに次のようなコードを追加して問題を解決しました。

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

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

私の最初の仮定は、オブジェクトのインスタンスを1つだけ作成する方が効率的であり、より良いメモリ管理が可能になるということでした。私がこの問題を調査して集めたことから、そうではありません。実際、APIへの各呼び出しを独立したスレッドセーフなイベントとして扱う方が効率的です。オブジェクトが範囲外になったときに、すべてのリソースが適切に解放されていることを確認します。

これは、WebサービスまたはREST AP​​Iとして公開することになる次の自然な進行にAPIを移行する場合に特に意味があります。

開示

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

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

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

    }
}

簡単な解決策は、コレクションを使用する前にコレクションに.ToList()を使用することです。 MARSはMySQLでは動作しません。

3
cen

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

たとえば(「Blog and Posts」サンプルエンティティを この回答 のように使用します)。

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を有効にせずに項目ごとに作業できるようになります。

サンプルに見られるように、これのもう一つの良い利点は、あなたがでさえ必要とされるであろうようにループの終わりまで(または他のそのような回避策)まで待たなくても各項目を通してループするときあなたが変更を保存できるということですMARSが有効になりました( here および here を参照)。

2
Paul

私の場合は、myContext.SaveChangesAsync()を呼び出す前に "await"ステートメントが欠落していることがわかりました。これらの非同期呼び出しが私のためにデータリーダーの問題を解決する前に待ちを追加してください。

1
Elijah Lofgren

私は同じエラーがあることに気付きました、そしてそれは私があなたのpredicateのためにFunc<TEntity, bool>の代わりにExpression<Func<TEntity, bool>>を使っていた時に起こりました。

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

EntityFramworkExpression'sを使って巧妙なことをしていますが、それは単にFunc'sを使ってはしていません。

1
sQuir3l

この問題を軽減する2つの解決策:

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

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

0
E. Staal

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

 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;
            }
0
Debendra Dash

条件の一部を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()で使用しようとすると例外がスローされます。代わりに次のようにPredicateを作成します。

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
Arvand