Varlık Çerçevesi: Bu Komutla ilişkilendirilmiş zaten açık bir DataReader var


285

Entity Framework kullanıyorum ve bazen bu hatayı alırım.

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

Düz-se bile herhangi bir manuel bağlantı yönetimi yapmıyorum.

bu hata zaman zaman meydana gelir.

hatayı tetikleyen kod (okuma kolaylığı nedeniyle kısaltılmıştır):

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

Her seferinde yeni bağlantı açmak için Desen ata seçeneğini kullanın.

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

}

hala problemli

EF zaten açıksa neden bir bağlantıyı tekrar kullanmayasınız?


1
Bu sorunun eski olduğunu anlıyorum, ancak sizin predicateve historicPredicatedeğişkenlerinizin ne olduğunu bilmek isterim . Geçmene eğer tesbit ettik Func<T, bool>için Where()(öyle çünkü "burada" bellekte) bazen işi derlemek ve edecektir. Ne olmalıdır yapıyor geçen bir Expression<Func<T, bool>>etmek Where().
James

Yanıtlar:


351

Bu bağlantıyı kapatmakla ilgili değil. EF bağlantıyı doğru yönetir. Bu sorunu anlıyorum, bir sonraki DataReader birincisi okumayı tamamlamadan önce yürütülürken, tek bağlantıda (veya birden çok seçimli tek bir komut) yürütülen birden çok veri alma komutunun olmasıdır. Kural dışı durumu önlemenin tek yolu, birden çok iç içe DataReaders = MultipleActiveResultSets özelliğini etkinleştirmektir. Bu her zaman gerçekleşen başka bir senaryo, sorgu (IQueryable) sonucunu yinelediğinizde ve yineleme içindeki yüklü varlık için tembel yüklemeyi tetikleyeceğinizdir.


2
bu mantıklı olurdu. ancak her yöntemde yalnızca bir seçim vardır.
Sonic Soul

1
@Sonic: Soru bu. Belki birden fazla komut yürütülür ama görmezsiniz. Bunun Profiler'de izlenip izlenemeyeceğinden emin değilim (ikinci okuyucu yürütülmeden önce istisna atılabilir). Ayrıca, sorguyu ObjectQuery'ye aktarmayı ve SQL komutunu görmek için ToTraceString'i çağırmayı deneyebilirsiniz. İzlemesi zor. Her zaman MARS'u açarım.
Ladislav Mrnka

2
@Sonic: Amacım yürütülen ve tamamlanan SQL komutlarını kontrol etmekti.
Ladislav Mrnka

11
harika, benim sorunum ikinci senaryo idi: 'Sorgunun sonucunu yinelediğinizde (IQueryable) ve yineleme içindeki yüklü varlık için tembel yüklemeyi tetikleyeceksiniz.'
Amr Elgarhy

6
Etkinleştirilmesi MARS olabilir görünüşte kötü yan etkileri vardır: designlimbo.com/?p=235
Søren Boisen

126

MARS (MultipleActiveResultSets) kullanmaya alternatif olarak, birden çok sonuç kümesi açmamanız için kodunuzu yazabilirsiniz.

Yapabileceğiniz şey verileri belleğe almak, böylece okuyucuyu açmayacaksınız. Genellikle başka bir sonuç kümesi açmaya çalışırken bir sonuç kümesi üzerinden yineleme yapılır.

Basit kod:

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

Diyelim ki veritabanınızda aşağıdakileri içeren bir arama yapıyorsunuz:

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

Bunun için .ToList () ekleyerek basit bir çözüm yapabiliriz :

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

Bu, varlık çerçevesini listeyi belleğe yüklemeye zorlar, böylece foreach döngüsünde yinelediğimizde artık listeyi açmak için veri okuyucusunu kullanmaz, bunun yerine bellekte olur.

Örneğin bazı özellikleri tembelleştirmek istiyorsanız bunun istenmeyebileceğini anlıyorum. Bu çoğunlukla umarım bu sorunu nasıl / neden alabileceğinizi açıklayan bir örnektir, böylece buna göre karar verebilirsiniz


7
Bu çözüm benim için çalıştı. Sorgudan hemen sonra ve sonuçla ilgili başka bir şey yapmadan önce .ToList () öğesini ekleyin.
TJKjaer

9
Buna dikkat edin ve sağduyunuzu kullanın. ToListBin nesne çekiyorsanız , hafızayı bir ton artıracaktır. Bu özel örnekte, iç sorguyu ilk ile birleştirmek daha iyi olur, böylece iki yerine yalnızca bir sorgu oluşturulur.
kamranicus

4
@subkamran Demek istediğim, sadece bir şey düşünmek değil, durum için neyin doğru olduğunu seçmekti. Örnek sadece açıklamak düşündüm rastgele bir şey :)
Jim Wolff

3
Kesinlikle, ben sadece kopyala / yapıştır mutlu millet için açıkça işaret etmek istedim :)
kamranicus

Beni vurma, ama bu hiçbir şekilde soruya bir çözüm değil. Ne zamandan beri "bellekte veri çekme" SQL ile ilgili bir sorun için bir çözüm? Ben veritabanı ile konuşmayı seviyorum, bu yüzden hiçbir şekilde "aksi takdirde bir SQL istisnası atılır" bellekte bir şey çekmek tercih etmem. Bununla birlikte, sağlanan kodda, veritabanına iki kez başvurmak için bir neden yoktur. Tek çağrıda yapılması kolay. Bunun gibi yayınlara dikkat edin. ToList, First, Single, ... Yalnızca bellekte veri gerektiğinde kullanılmalıdır (bu nedenle yalnızca u İSTENEN veriler), aksi takdirde bir SQL istisnası oluştuğunda kullanılmamalıdır.
Frederik Prijck

70

Bu sorunun üstesinden gelmenin başka bir yolu var. Bunun daha iyi bir yol olup olmadığı durumunuza bağlıdır.

Sorun tembel yüklemeden kaynaklanmaktadır, bu nedenle bundan kaçınmanın bir yolu, Dahil Et'i kullanarak tembel yüklemeye sahip olmamaktır:

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

Uygun Includeolanları kullanırsanız, MARS'yi etkinleştirmekten kaçınabilirsiniz. Ancak birini kaçırırsanız hatayı alırsınız, bu nedenle MARS'yi etkinleştirmek muhtemelen düzeltmenin en kolay yoludur.


1
Bir cazibe gibi çalıştı. .IncludeMARS'yi etkinleştirmekten çok daha iyi bir çözümdür ve kendi SQL sorgu kodunuzu yazmaktan çok daha kolaydır.
Mart'ta Nolonar

15
Herkes lambda değil sadece .Include ("dize") yazabilirsiniz sorunu yaşıyorsanız, uzantısı yöntemi orada bulunduğu için "System.Data.Entity kullanarak" eklemeniz gerekir.
Jim Wolff

46

Bu hatayı alıyorsunuz, yinelemeye çalıştığınız koleksiyon bir çeşit tembel yükleme (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

IQueriable koleksiyonunu diğer numaralandırılabilir koleksiyona dönüştürmek bu sorunu çözecektir. misal

_dbContext.Users.ToList()

Not: .ToList () her seferinde yeni bir küme oluşturur ve büyük verilerle uğraşıyorsanız performans sorununa neden olabilir.


1
Mümkün olan en kolay çözüm! Big UP;)
Jacob Sobus

1
Sınırsız listelerin getirilmesi ciddi performans sorunlarına neden olabilir! Bunu nasıl onaylayabiliriz?
SandRock

1
@SandRock küçük bir şirkette çalışan biri için değil - SELECT COUNT(*) FROM Users= 5
Simon_Weaver

5
Bunun hakkında iki kez düşünün. Bu Soru / Cevap'yı okuyan genç bir geliştirici, bunun kesinlikle olmadığı zaman tüm zamanların bir çözümü olduğunu düşünebilir. Db'den sınırsız listeler getirme tehlikesi hakkında okuyucuları uyarmak için cevabınızı düzenlemenizi öneririz.
SandRock,

1
@SandRock Bu, en iyi uygulamaları açıklayan bir yanıt veya makaleyi bağlamanız için iyi bir yer olacağını düşünüyorum.
Sinjai

13

Yapıcıya seçeneği ekleyerek sorunu kolayca (pragmatik) çözdüm. Böylece, sadece gerektiğinde kullanıyorum.

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

2
Teşekkür ederim. İşe yarıyor. Ben sadece web.config doğrudan bağlantı dizesine MultipleActiveResultSets = true ekledim
Mosharaf Hossain

11

Ayarlamak için bağlantı dizenizde deneyin MultipleActiveResultSets=true. Bu, veritabanında çoklu göreve izin verir.

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

Bu benim için işe yarar ... app.config'deki bağlantınız veya programlı olarak ayarlasanız da ...


MultipleActiveResultSets = bağlantı dizenize eklenen true, büyük olasılıkla sorunu çözecektir. Bu aşağı oy kullanılmamalıydı.
Aaron Hudon

evet emin ave nasıl bağlantı dizesi eklemek gösterilmiştir
Mohamed Hocine

4

Başlangıçta MyDataContext nesnesinin (Burada MyDataContext bir EF5 Context nesnesidir) bir örneğine başvurmak için API sınıfımda statik bir alan kullanmaya karar vermiştim, ancak bu sorunu yaratmak gibi görünüyordu. API yöntemlerimin her birine aşağıdaki gibi bir kod ekledim ve bu sorunu çözdü.

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

Diğer insanların belirttiği gibi, EF Data Context nesneleri iş parçacığı için güvenli DEĞİLDİR. Bu nedenle, bunları statik nesneye yerleştirmek en sonunda doğru koşullarda "veri okuyucu" hatasına neden olacaktır.

Orijinal varsayımım, nesnenin yalnızca bir örneğini oluşturmanın daha verimli olacağı ve daha iyi bellek yönetimi sağlayabileceğiydi. Bu konuyu araştırmak için topladığım şeyden, durum böyle değil. Aslında, API'nıza yapılan her bir çağrıyı izole edilmiş, iş parçacığı açısından güvenli bir olay olarak ele almak daha verimli görünmektedir. Nesne kapsam dışına çıktığında, tüm kaynakların düzgün bir şekilde serbest bırakılmasını sağlamak.

Bu, özellikle API'nizi bir WebService veya REST API'si olarak gösterecek bir sonraki doğal ilerlemeye götürürseniz mantıklıdır.

ifşa

  • İşletim Sistemi: Windows Server 2012
  • .NET: Yüklü 4.5, 4.0 kullanarak proje
  • Veri Kaynağı: MySQL
  • Uygulama Çerçevesi: MVC3
  • Kimlik Doğrulama: Formlar

3

Görünüme bir IQueriable gönderdiğimde ve iç foreach'in de bağlantıyı kullanması gereken bir çift foreach içinde kullandığımda bu hatanın meydana geldiğini fark ettim. Basit örnek (ViewBag.parents IQueriable veya DbSet olabilir):

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

    }
}

Basit çözüm, .ToList()koleksiyonu kullanmadan önce koleksiyonda kullanmaktır. Ayrıca MARS'ın MySQL ile çalışmadığını unutmayın.


TEŞEKKÜR EDERİM! Burada her şey "iç içe döngüler sorun" dedi ama kimse nasıl düzeltileceğini söyledi. Ben ToList()DB bir koleksiyon almak için ilk çağrı koymak . Sonra foreachbu listede bir yaptım ve sonraki çağrılar hata vermek yerine mükemmel çalıştı.
AlbatrossCafe

@AlbatrossCafe ... ancak hiç kimse bu durumda verilerinizin belleğe yükleneceğini ve sorgulamanın bellekte yürütüleceğini söyleyemez, DB
Lightning3

3

Ben de aynı hatayı buldum ve senin Func<TEntity, bool>yerine bir yerine kullanırken meydana geldi .Expression<Func<TEntity, bool>>predicate

Ben hepsini değiştirdi kez Func'skarşı Expression'sistisna atılan durdurdu.

Ben inanıyorum EntityFramworkbazı zekice şeyler yapar Expression'sbasitçe ile ilgisi olmadığıFunc's


Bunun için daha fazla oy gerekiyor. (MyTParent model, Func<MyTChildren, bool> func)Benim ViewModels whereGenel DataContext yöntemi için belirli bir yan tümce belirtebilirsiniz böylece alarak DataContext sınıfında bir yöntem zanaat çalışıyordu . Bunu yapana kadar hiçbir şey çalışmıyordu.
Justin

3

Bu sorunu azaltmak için 2 çözüm:

  1. .ToList()Sorgunuzdan sonra tembel yüklemeyi koruyarak bellek önbelleğe almayı zorlayın , böylece yeni bir DataReader açarak tekrarlayabilirsiniz.
  2. .Include(/ sorguya yüklemek istediğiniz ek varlıklar /) buna istekli yükleme adı verilir, bu da DataReader ile bir sorguyu yürütürken ilişkili nesneleri (varlıkları) eklemenize izin verir.

2

MARS'yi etkinleştirmek ve tüm sonuç kümesini belleğe almak arasında iyi bir orta yol, yalnızca bir ilk sorgudaki kimlikleri almak ve daha sonra gittiğiniz her öğeyi gerçekleştiren kimlikler arasında geçiş yapmaktır.

Örneğin ( bu yanıttaki gibi "Blog ve Yayınlar" örnek varlıklarını kullanarak ):

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

Bunu yapmak, binlerce nesne nesnesinin aksine, sadece birkaç bin tamsayıyı belleğe çekebileceğiniz anlamına gelir; bu, MARS'yi etkinleştirmeden tek tek çalışmanızı sağlarken bellek kullanımını en aza indirmelidir.

Bunun bir diğer güzel yararı, örnekte görüldüğü gibi, döngünün sonuna kadar (veya başka bir geçici çözüm) beklemek zorunda kalmadan, her öğeye döngü yaptığınızda değişiklikleri kaydedebilmenizdir. MARS etkin ( buraya ve buraya bakınız ).


context.SaveChanges();İç döngü :(. Bu iyi değil. Döngünün dışında olmalı.
Jawand Singh

1

Benim durumumda, myContext.SaveChangesAsync () çağrıları öncesinde eksik "bekliyor" ifadeleri olduğunu buldum. Bu zaman uyumsuz çağrılardan önce beklemek, veri okuyucu sorunlarını benim için düzeltti.


0

Koşullarımızın bir kısmını bir Func <> veya uzantı yönteminde gruplandırmaya çalışırsak, bu hatayı alırız, şöyle bir kodumuz olduğunu varsayalım:

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

Bunu bir Where () içinde kullanmaya çalışırsak istisna atar, bunun yerine yapmamız gereken şey böyle bir Predicate oluşturmaktır:

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

Daha fazla bilgi için: http://www.albahari.com/nutshell/predicatebuilder.aspx


0

Bu sorun, verileri bir listeye dönüştürerek çözülebilir

 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 () çağrıyı yapar ancak yukarıdaki kod yine de bağlantıyı atmaz. bu nedenle _webcontext'iniz satır 1 sırasında kapalı olma riski altındadır
Sonic Soul

0

Benim durumumda, bağımlılık enjeksiyon kaydı nedeniyle sorun oluştu. Tek bir kayıtlı hizmete bir dbcontext kullanıyordu istek başına hizmet enjekte edildi. Bu nedenle, dbcontext çoklu istek ve dolayısıyla hata içinde kullanılmıştır.


0

Benim durumumda sorunun MARS bağlantı dizesi ile ilgisi yoktu ama json serileştirmesi. Projemi NetCore2'den 3'e yükselttikten sonra bu hatayı aldım.

Daha fazla bilgiyi burada bulabilirsiniz


-6

Ben ikinci sorgu önce kodun aşağıdaki bölümünü kullanarak bu sorunu çözdü:

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

uyku süresini milisaniye cinsinden değiştirebilirsiniz

PD Dişleri kullanırken kullanışlıdır


13
Herhangi bir çözümde keyfi olarak Thread.Sleep eklemek kötü bir uygulamadır - ve bazı değerlerin durumunun tamamen anlaşılmadığı farklı bir sorunu ortadan kaldırmak için kullanıldığında özellikle kötüdür. Yanıtın alt kısmında belirtildiği gibi "İş Parçacığı Kullanmanın" en azından bazı temel diş çekme anlayışına sahip olmak anlamına geleceğini düşünürdüm - ancak bu yanıt, özellikle çok kötü bir fikir olduğu durumlarda herhangi bir bağlamı dikkate almaz - UI iş parçacığında olduğu gibi.
Mike Tours
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.