Kerangka Entitas: Sudah ada DataReader terbuka yang terkait dengan Perintah ini

285

Saya menggunakan Entity Framework dan kadang-kadang saya akan mendapatkan kesalahan ini.

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

Meskipun saya tidak melakukan manajemen koneksi manual.

kesalahan ini terjadi sesekali.

kode yang memicu kesalahan (disingkat agar mudah dibaca):

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

menggunakan pola Buang untuk membuka koneksi baru setiap kali.

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());
    }

}

masih bermasalah

mengapa EF tidak akan menggunakan kembali koneksi jika sudah terbuka.

Jiwa Sonic
sumber
1
Saya menyadari bahwa pertanyaan ini kuno, tetapi saya tertarik untuk mengetahui jenis predicatedan historicPredicatevariabel Anda. Saya telah menemukan bahwa jika Anda lulus Func<T, bool>untuk Where()itu akan mengkompilasi dan kadang-kadang bekerja (karena melakukan "di mana" dalam memori). Apa yang Anda harus lakukan adalah lewat Expression<Func<T, bool>>untuk Where().
James

Jawaban:

351

Ini bukan tentang menutup koneksi. EF mengelola koneksi dengan benar. Pemahaman saya tentang masalah ini adalah bahwa ada beberapa perintah pengambilan data yang dieksekusi pada koneksi tunggal (atau perintah tunggal dengan banyak pilihan) sementara DataReader berikutnya dieksekusi sebelum yang pertama menyelesaikan pembacaan. Satu-satunya cara untuk menghindari pengecualian adalah untuk memungkinkan multiple DataReaders bersarang = nyalakan MultipleActiveResultSets. Skenario lain ketika ini selalu terjadi adalah ketika Anda beralih melalui hasil kueri (IQueryable) dan Anda akan memicu pemuatan malas untuk entitas yang dimuat di dalam iterasi.

Ladislav Mrnka
sumber
2
itu masuk akal. tetapi hanya ada satu pilih dalam setiap metode.
Sonic Soul
1
@ Sinon: Itulah pertanyaannya. Mungkin ada lebih dari satu perintah yang dieksekusi tetapi Anda tidak melihatnya. Saya tidak yakin apakah ini dapat dilacak di Profiler (pengecualian dapat dilemparkan sebelum pembaca kedua dijalankan). Anda juga dapat mencoba untuk melemparkan kueri ke ObjectQuery dan memanggil ToTraceString untuk melihat perintah SQL. Sulit dilacak. Saya selalu menyalakan MARS.
Ladislav Mrnka
2
@ Sinon: Tidak ada maksud saya adalah untuk memeriksa perintah SQL yang dieksekusi dan diselesaikan.
Ladislav Mrnka
11
hebat, masalah saya adalah skenario kedua: 'ketika Anda beralih melalui hasil kueri (IQueryable) dan Anda akan memicu pemuatan malas untuk entitas yang dimuat di dalam iterasi.'
Amr Elgarhy
6
Mengaktifkan MARS tampaknya dapat memiliki efek samping yang buruk: designlimbo.com/?p=235
Søren Boisen
126

Atau dengan menggunakan MARS (MultipleActiveResultSets) Anda dapat menulis kode Anda sehingga Anda tidak membuka beberapa set hasil.

Yang dapat Anda lakukan adalah mengambil data ke memori, sehingga pembaca tidak akan terbuka. Ini sering disebabkan oleh iterasi melalui resultset ketika mencoba untuk membuka set hasil lainnya.

Kode sampel:

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; }
}

Katakanlah Anda sedang melakukan pencarian di database Anda yang berisi ini:

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"));
}

Kita dapat melakukan solusi sederhana untuk ini dengan menambahkan .ToList () seperti ini:

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

Ini memaksa entitas untuk memuat daftar ke dalam memori, jadi ketika kita mengulanginya meskipun dalam loop foreach itu tidak lagi menggunakan pembaca data untuk membuka daftar, itu malah dalam memori.

Saya menyadari bahwa ini mungkin tidak diinginkan jika Anda ingin malas memuat beberapa properti misalnya. Ini sebagian besar adalah contoh yang diharapkan menjelaskan bagaimana / mengapa Anda mungkin mendapatkan masalah ini, sehingga Anda dapat membuat keputusan yang sesuai

Jim Wolff
sumber
7
Solusi ini berhasil untuk saya. Tambahkan .ToList () tepat setelah kueri dan sebelum melakukan hal lain dengan hasilnya.
TJKjaer
9
Hati-hati dengan ini dan gunakan akal sehat. Jika Anda ToListmemasukkan ribuan objek, itu akan menambah memori satu ton. Dalam contoh khusus ini, Anda sebaiknya menggabungkan permintaan dalam dengan yang pertama sehingga hanya satu permintaan yang dihasilkan daripada dua.
kamranicus
4
@ Subkamran Maksud saya persis seperti itu, memikirkan sesuatu dan memilih apa yang tepat untuk situasi tersebut, bukan hanya melakukan. Contohnya adalah sesuatu yang acak yang saya pikir untuk jelaskan :)
Jim Wolff
3
Jelas, saya hanya ingin menunjukkannya secara eksplisit untuk orang-orang yang senang menyalin / menempel-senang :)
kamranicus
Jangan tembak aku, tapi ini sama sekali bukan solusi untuk pertanyaan itu. Sejak kapan "menarik data dalam memori" solusi untuk masalah terkait SQL? Saya suka cerewet dengan database, jadi saya tidak akan memilih untuk menarik sesuatu di memori "karena jika tidak ada pengecualian SQL dilemparkan". Namun demikian, dalam kode yang Anda berikan, tidak ada alasan untuk menghubungi database dua kali. Mudah dilakukan dalam satu panggilan. Hati-hati dengan posting seperti ini. ToList, First, Single, ... Seharusnya hanya digunakan ketika data diperlukan dalam memori (jadi hanya data yang INGIN), bukan ketika pengecualian SQL terjadi sebaliknya.
Frederik Prijck
70

Ada cara lain untuk mengatasi masalah ini. Apakah itu cara yang lebih baik tergantung pada situasi Anda.

Masalahnya adalah hasil dari pemuatan malas, jadi salah satu cara untuk menghindarinya adalah tidak memiliki pemuatan malas, melalui penggunaan Sertakan:

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

Jika Anda menggunakan yang sesuai Include, Anda dapat menghindari mengaktifkan MARS. Tetapi jika Anda melewatkannya, Anda akan mendapatkan kesalahan, jadi mengaktifkan MARS mungkin adalah cara termudah untuk memperbaikinya.

Ryan Lundy
sumber
1
Bekerja seperti pesona. .Includeadalah solusi yang jauh lebih baik daripada mengaktifkan MARS, dan jauh lebih mudah daripada menulis kode kueri SQL Anda sendiri.
Nolonar
15
Jika ada yang mengalami masalah yang Anda hanya bisa menulis .Include ("string") bukan lambda, Anda perlu menambahkan "using System.Data.Entity" karena metode ekstensi terletak di sana.
Jim Wolff
46

Anda mendapatkan kesalahan ini, ketika koleksi yang Anda coba untuk beralih adalah jenis pemuatan malas (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

Mengubah koleksi IQueriable menjadi koleksi enumerable lainnya akan menyelesaikan masalah ini. contoh

_dbContext.Users.ToList()

Catatan: .ToList () membuat set baru setiap waktu dan itu dapat menyebabkan masalah kinerja jika Anda berurusan dengan data besar.

Nalan Madheswaran
sumber
1
Solusi termudah yang mungkin! Big UP;)
Jacob Sobus
1
Mengambil daftar yang tidak terikat dapat menyebabkan masalah kinerja yang parah! Bagaimana bisa ada orang yang meneguhkan hal itu?
SandRock
1
@ SandRock bukan untuk seseorang yang bekerja untuk perusahaan kecil - SELECT COUNT(*) FROM Users= 5
Simon_Weaver
5
Pikirkan dua kali tentang itu. Pengembang muda yang membaca T / A ini mungkin berpikir ini adalah solusi sepanjang masa padahal sebenarnya tidak. Saya sarankan Anda mengedit jawaban Anda untuk memperingatkan pembaca tentang bahaya mengambil daftar yang tidak terikat dari db.
SandRock
1
@ SandRock Saya pikir ini akan menjadi tempat yang baik bagi Anda untuk menautkan jawaban atau artikel yang menggambarkan praktik terbaik.
Sinjai
13

Saya memecahkan masalah dengan mudah (pragmatis) dengan menambahkan opsi ke konstruktor. Jadi, saya menggunakannya hanya saat dibutuhkan.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...
Harvey Triana
sumber
2
Terima kasih. Bekerja. Saya baru saja menambahkan MultipleActiveResultSets = true dalam string koneksi langsung di web.config
Mosharaf Hossain
11

Coba di string koneksi Anda untuk mengatur MultipleActiveResultSets=true. Ini memungkinkan multitasking pada basis data.

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

Itu berfungsi untuk saya ... apakah koneksi Anda di app.config atau Anda mengaturnya secara terprogram ... semoga ini membantu

Mohamed Hocine
sumber
MultipleActiveResultSets = true ditambahkan ke string koneksi Anda kemungkinan akan menyelesaikan masalah. Ini seharusnya tidak dipilih.
Aaron Hudon
ya tentu saya ave menunjukkan cara menambahkan ke string koneksi Anda
Mohamed Hocine
4

Saya awalnya memutuskan untuk menggunakan bidang statis di kelas API saya untuk referensi contoh objek MyDataContext (Di mana MyDataContext adalah objek Konteks EF5), tetapi itulah yang tampaknya menciptakan masalah. Saya menambahkan kode seperti berikut ke setiap metode API saya dan itu memperbaiki masalahnya.

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

Seperti yang dinyatakan orang lain, objek Konteks Data EF TIDAK thread aman. Jadi menempatkan mereka di objek statis pada akhirnya akan menyebabkan kesalahan "pembaca data" di bawah kondisi yang tepat.

Asumsi awal saya adalah bahwa membuat hanya satu instance objek akan lebih efisien, dan memberikan manajemen memori yang lebih baik. Dari apa yang saya kumpulkan meneliti masalah ini, bukan itu masalahnya. Bahkan, tampaknya lebih efisien untuk memperlakukan setiap panggilan ke API Anda sebagai peristiwa aman yang terisolasi. Memastikan bahwa semua sumber daya dirilis dengan benar, karena objek keluar dari ruang lingkup.

Ini masuk akal terutama jika Anda membawa API Anda ke perkembangan alami berikutnya yang akan mengeksposnya sebagai WebService atau REST API.

Penyingkapan

  • OS: Windows Server 2012
  • .NET: Diinstal 4,5, Proyek menggunakan 4.0
  • Sumber Data: MySQL
  • Kerangka Aplikasi: MVC3
  • Otentikasi: Formulir
Jeffrey A. Gochin
sumber
3

Saya perhatikan bahwa kesalahan ini terjadi ketika saya mengirim IQueriable ke tampilan dan menggunakannya dalam double foreach, di mana foreach bagian dalam juga perlu menggunakan koneksi. Contoh sederhana (ViewBag.parents dapat IQueriable atau DbSet):

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

    }
}

Solusi sederhana adalah untuk digunakan .ToList()pada koleksi sebelum menggunakannya. Perhatikan juga bahwa MARS tidak berfungsi dengan MySQL.

cen
sumber
TERIMA KASIH! Semua yang ada di sini mengatakan "loop bersarang adalah masalahnya" tetapi tidak ada yang mengatakan bagaimana cara memperbaikinya. Saya melakukan ToList()panggilan pertama untuk mendapatkan koleksi dari DB. Kemudian saya melakukan foreachpada daftar itu dan panggilan berikutnya berfungsi dengan baik alih-alih memberikan kesalahan.
AlbatrossCafe
@AlbatrossCafe ... tetapi tidak ada yang menyebutkan bahwa dalam hal ini data Anda akan dimuat ke memori dan permintaan akan dieksekusi dalam memori, bukan DB
Lightning3
3

Saya menemukan bahwa saya memiliki kesalahan yang sama, dan itu terjadi ketika saya menggunakan Func<TEntity, bool>bukan Expression<Func<TEntity, bool>>untuk Anda predicate.

Setelah saya berubah semua Func'suntuk Expression'spengecualian berhenti yang dilemparkan.

Saya percaya bahwa EntityFramworkmelakukan beberapa hal pintar Expression'syang tidak ada hubungannya dengan ituFunc's

sQuir3l
sumber
Ini membutuhkan lebih banyak upvotes. Saya mencoba membuat metode dalam kelas DataContext saya mengambil (MyTParent model, Func<MyTChildren, bool> func)sehingga ViewModels saya dapat menentukan whereklausa tertentu untuk metode Generic DataContext. Tidak ada yang berhasil sampai saya melakukan ini.
Justin
3

2 solusi untuk mengatasi masalah ini:

  1. Memaksa cache memori menjaga pemuatan malas dengan .ToList()setelah kueri Anda, sehingga Anda kemudian dapat mengulanginya dengan membuka DataReader baru.
  2. .Include(/ entitas tambahan yang ingin Anda muat dalam query /) ini disebut eager loading, yang memungkinkan Anda untuk (memang) memasukkan objek terkait (entitas) selama ia mengeksekusi query dengan DataReader.
Stefano Beltrame
sumber
2

Jalan tengah yang baik antara mengaktifkan MARS dan mengambil seluruh hasil yang diatur ke dalam memori adalah untuk mengambil hanya ID di kueri awal, dan kemudian loop melalui ID mematerialisasi setiap entitas saat Anda pergi.

Misalnya (menggunakan entitas sampel "Blog dan Posting" seperti dalam jawaban ini ):

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();
    }
}

Melakukan ini berarti Anda hanya menarik beberapa ribu bilangan bulat ke dalam memori, berbeda dengan ribuan keseluruhan grafik objek, yang seharusnya meminimalkan penggunaan memori sambil memungkinkan Anda untuk mengerjakan item-per-item tanpa mengaktifkan MARS.

Manfaat lain yang bagus dari ini, seperti yang terlihat dalam sampel, adalah Anda dapat menyimpan perubahan saat Anda mengulangi setiap item, daripada harus menunggu sampai akhir loop (atau solusi lain seperti itu), seperti yang akan diperlukan bahkan dengan MARS diaktifkan (lihat di sini dan di sini ).

Paul
sumber
context.SaveChanges();inside loop :(. Ini tidak bagus. Itu pasti di luar loop.
Jawand Singh
1

Dalam kasus saya, saya menemukan bahwa ada pernyataan "menunggu" yang hilang sebelum panggilan myContext.SaveChangesAsync (). Menambahkan menunggu sebelum panggilan async itu memperbaiki masalah pembaca data untuk saya.

Elia Lofgren
sumber
0

Jika kita mencoba mengelompokkan sebagian kondisi kita menjadi Fungsi <> atau metode ekstensi, kita akan mendapatkan kesalahan ini, misalkan kita memiliki kode seperti ini:

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) { .... }

Ini akan membuang pengecualian jika kita mencoba menggunakannya di Where (), yang harus kita lakukan adalah membangun Predikat seperti ini:

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

Selanjutnya dapat dibaca di: http://www.albahari.com/nutshell/predicatebuilder.aspx

Arvand
sumber
0

Masalah ini dapat diatasi hanya dengan mengubah data menjadi daftar

 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;
            }
Debendra Dash
sumber
ToList () melakukan panggilan tetapi kode di atas masih tidak membuang koneksi. jadi _webcontext Anda masih berisiko ditutup pada saat baris 1
Sonic Soul
0

Dalam situasi saya masalah terjadi karena registrasi injeksi ketergantungan. Saya menyuntikkan layanan per permintaan lingkup yang menggunakan dbcontext ke layanan tunggal terdaftar. Karenanya dbcontext digunakan dalam banyak permintaan dan karenanya kesalahan.

E. Staal
sumber
0

Dalam kasus saya masalah ini tidak ada hubungannya dengan string koneksi MARS tetapi dengan serialisasi json. Setelah memutakhirkan proyek saya dari NetCore2 ke 3 saya mendapatkan kesalahan ini.

Informasi lebih lanjut dapat ditemukan di sini

OrElse
sumber
-6

Saya memecahkan masalah ini menggunakan bagian kode berikut sebelum permintaan kedua:

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

Anda dapat mengubah waktu tidur dalam milidetik

PD Berguna saat menggunakan utas

i31nGo
sumber
13
Menambahkan Thread secara sewenang-wenang. Tidur dalam solusi apa pun adalah praktik yang buruk - dan khususnya buruk ketika digunakan untuk menghindari masalah yang berbeda di mana keadaan beberapa nilai tidak sepenuhnya dipahami. Saya akan berpikir bahwa "Menggunakan Thread" sebagaimana dinyatakan di bagian bawah respons berarti memiliki setidaknya beberapa pemahaman dasar tentang threading - tetapi respons ini tidak memperhitungkan konteks apa pun, terutama keadaan di mana itu adalah ide yang sangat buruk untuk menggunakan Thread. Tidur - seperti pada utas UI.
Tur Mike