Veri kaydı satırını verir ve tüm kayıtları okumazsak Veritabanı bağlantısı kapatılır mı?


15

yieldAnahtar kelimenin nasıl çalıştığını anlarken , StackOverflow üzerinde Link1 ve link2 ile karşılaştım yield return. Ama yield returnaşağıda gösterildiği gibi kullanırsam ve tüm DataReader üzerinden yineleme yapmazsam, DB bağlantısı sonsuza kadar açık kalacak mı?

IEnumerable<IDataRecord> GetRecords()
{
    SqlConnection myConnection = new SqlConnection(@"...");
    SqlCommand myCommand = new SqlCommand(@"...", myConnection);
    myCommand.CommandType = System.Data.CommandType.Text;
    myConnection.Open();
    myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
    try
       {               
          while (myReader.Read())
          {
            yield return myReader;          
          }
        }
    finally
        {
            myReader.Close();
        }
}


void AnotherMethod()
{
    foreach(var rec in GetRecords())
    {
       i++;
       System.Console.WriteLine(rec.GetString(1));
       if (i == 5)
         break;
  }
}

Aynı örneği bir örnek Konsol App denedim ve sonunda bloğunun GetRecords()yürütülmediğini hata ayıklama sırasında fark ettim . DB Bağlantısının kapatılmasını nasıl sağlayabilirim? yieldAnahtar kelime kullanmaktan daha iyi bir yol var mı ? Ben DB üzerinde seçili SQL'leri ve saklı yordamları yürütmekten sorumlu olacak ve sonuç dönecektir özel bir sınıf tasarlamak çalışıyorum. Ancak DataReader'ı arayana geri vermek istemiyorum. Ayrıca bağlantının tüm senaryolarda kapatıldığından emin olmak istiyorum.

Düzenle Yöntem arayanların yöntemi doğru kullanmasını beklemek yanlış olduğu için Ben'in cevabının cevabını değiştirdi ve DB bağlantısına göre, yöntemin sebepsiz olarak birden çok kez çağrılması daha pahalı olacak.

Ayrıntılı açıklama için Jakob ve Ben'e teşekkür ederiz.


Gördüğüm kadarıyla ilk etapta bağlantıyı
paparazzo

@Frisbee Düzenlendi :)
GawdePrasad

Yanıtlar:


12

Evet, açıkladığınız sorunla karşılaşacaksınız: sonucun yinelemesini bitirinceye kadar bağlantıyı açık tutacaksınız. Bununla başa çıkmayı düşünebileceğim iki genel yaklaşım var:

İt, çekme

Şu anda IEnumerable<IDataRecord>, çekebileceğiniz bir veri yapısı döndürüyorsunuz . Bunun yerine, sonuçlarını dışarı itmek için yönteminizi değiştirebilirsiniz . En basit yol Action<IDataRecord>, her bir yinelemede çağrılan bir geçiş yapmaktır:

void GetRecords(Action<IDataRecord> callback)
{
    // ...
      while (myReader.Read())
      {
        callback(myReader);
      }
}

Bir öğe koleksiyonu ile uğraştığınız göz önüne alındığında, IObservable/ IObserverbiraz daha uygun bir veri yapısı olabilir, ancak ihtiyacınız yoksa, basit Actionbir çok daha basittir.

Hevesle Değerlendirin

Bir alternatif, geri dönmeden önce yinelemenin tamamen tamamlandığından emin olmaktır.

Normalde, sonuçları yalnızca bir listeye koyarak sonra geri göndererek yapabilirsiniz, ancak bu durumda her öğenin okuyucuya aynı referans olmasıyla ilgili ek bir karmaşıklık vardır. Dolayısıyla, okuyucudan ihtiyacınız olan sonucu çıkarmak için bir şeye ihtiyacınız var:

IEnumerable<T> GetRecords<T>(Func<IDataRecord,T> extractor)
{
    // ...
     var result = new List<T>();
     try
     {
       while (myReader.Read())
       {
         result.Add(extractor(myReader));         
       }
     }
     finally
     {
         myReader.Close();
     }
     return result;
}

Eagerly Evaluate adlı yaklaşımınızı kullanıyorum. Benim SQLCommand'ın Datareader birden çok çıkış (myReader.NextResult () gibi) döndürür ve ben bir liste listesi oluşturmak şimdi bir bellek yükü olacak? Veya veri okuyucuyu arayana [kişisel olarak tercih etmiyorum] göndermek çok verimli olur mu? [ancak bağlantı daha uzun süre açık olacak]. Burada tam olarak kafam karıştı, bir liste listesi oluşturmaktan daha uzun süre bağlantıyı açık tutma dengesi arasında. Web sayfamı ziyaret eden ve DB'ye bağlanan 500'den fazla kişinin güvenle olacağını varsayabilirsiniz.
GawdePrasad

1
@GawdePrasad Arayanın okuyucu ile ne yapacağına bağlıdır. Sadece bir koleksiyonun içindeki öğeleri koyarsa, önemli bir ek yük yoktur. Tüm değerlerin bellekte tutulmasını gerektirmeyen bir işlem yaparsa, bir bellek ek yükü vardır. Ancak çok fazla miktarda saklamadığınız sürece, umursamanız gereken bir ek yük değildir. Her iki durumda da, önemli bir zaman yükü olmamalıdır.
Ben Aaronson

"İtme, çekme" yaklaşımı "sonucun yinelemesini bitirinceye kadar bağlantıyı açık tutacaksınız" sorununu nasıl çözer? Bu yaklaşımla bile yinelemeyi bitirene kadar bağlantı hala açık, değil mi?
BornToCode

1
@BornToCode Şu soruya bakın: "aşağıda gösterildiği gibi verim dönüşü kullanırsam ve tüm DataReader üzerinden yineleme yapmazsam DB bağlantısı sonsuza kadar açık kalır". Sorun şu ki bir IEnumerable ve onu işleyen her arayan bu özel gotcha farkında olmalıdır: "Eğer benim yinelemeyi bitirmek bitmez, bir bağlantı açık tutacağım". Push yönteminde, bu sorumluluğu arayandan uzaklaştırırsınız - kısmen yineleme seçeneği yoktur. Zaman kontrolü onlara geri döndüğünde, bağlantı kapanır.
Ben Aaronson

1
Bu mükemmel bir cevap. Ben itme yaklaşımını seviyorum, çünkü sayfalanmış bir şekilde sunduğunuz büyük bir sorgu varsa, bir Eylem <> yerine bir Func <> ileterek hız için daha önce okumayı iptal edebilirsiniz; bu GetRecords işlevini sayfalama yapmaktan daha temiz hisseder.
Whelkaholism

10

Kişisel finallybloğu hep çalıştırır.

yield returnDerleyiciyi kullandığınızda durum makinesi uygulamak için yeni bir iç içe sınıf oluşturur.

Bu sınıf, finallyayrı yöntemler olarak tüm kod bloklarını içerecektir . finallyDuruma bağlı olarak yapılması gereken blokları takip edecektir . Gerekli tüm finallybloklar Disposeyöntemde yürütülecektir .

C # dili şartnamesine göre, foreach (V v in x) embedded-statementgenişletilir

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            V v = (V)(T)e.Current;
            embedded - statement
        }
    }
    finally {
         // Dispose e
    }
}

listeleyicisi Birlikte döngü çıkmak bile bertaraf edilecektir böylece breakya return.

Yineleyici uygulaması hakkında daha fazla bilgi almak için Jon Skeet'in bu makalesini okuyabilirsiniz.

Düzenle

Bu yaklaşımla ilgili sorun, yöntemi doğru kullanmak için sınıfınızın istemcilerine güvenmenizdir. Örneğin, numaralandırıcıyı doğrudan alabilir ve whileatmadan bir döngü içinde yineleyebilirler .

@BenAaronson tarafından önerilen çözümlerden birini düşünmelisiniz.


1
Bu son derece güvenilir değil. Örneğin: var records = GetRecords(); var first = records.First(); var count = records.Count()bağlantıyı ve okuyucuyu her seferinde açıp kapatarak bu yöntemi iki kez uygulayacaktır. İki kez tekrarlamak aynı şeyi yapar. Ayrıca, sonucu gerçekten yinelemeden önce uzun bir süre geçirebilirsiniz. Yöntem imzasındaki hiçbir şey bu tür bir davranış anlamına gelmez
Ben Aaronson

2
@ BenAaronson Cevabımdaki mevcut uygulama hakkında özel soruya odaklandım. Tüm soruna bakarsanız, cevabınızda önerdiğiniz yöntemi kullanmanın daha iyi olduğunu kabul ediyorum.
Jakub Lortz

1
@JakubLortz Yeterince adil
Ben Aaronson

2
@EsbenSkovPedersen Genellikle aptal geçirmez bir şey yapmaya çalıştığınızda, bazı işlevleri kaybedersiniz. Dengelemeniz gerekiyor. Burada sonuçları hevesle yükleyerek fazla kaybetmiyoruz. Her neyse, benim için en büyük sorun adlandırma. GetRecordsDönen adında bir yöntem IEnumerablegördüğümde, kayıtları belleğe yüklemesini beklerim. Öte yandan, bir özellik Recordsolsaydı, veritabanı üzerinde bir tür soyutlama olduğunu varsayalım.
Jakub Lortz

1
@EsbenSkovPedersen Nvoigs'in cevabı hakkındaki yorumlarıma bakın. Genel olarak, tekrarlandığında önemli işler yapacak bir IEnumerable döndüren yöntemlerle hiçbir sorunum yok, ancak bu durumda yöntem imzasının etkilerine uymuyor gibi görünüyor
Ben Aaronson

5

Belirli yielddavranıştan bağımsız olarak, kodunuz kaynaklarınızı doğru bir şekilde atmayacak yürütme yolları içerir. İkinci satırınız bir istisna veya üçüncünüz atarsa ​​ne olur? Ya da dördüncüsü? Çok karmaşık bir try / nihayet zincirine ihtiyacınız olacak veya usingblokları kullanabilirsiniz .

IEnumerable<IDataRecord> GetRecords()
{
    using(var connection = new SqlConnection(@"..."))
    {
        connection.Open();

        using(var command = new SqlCommand(@"...", connection);
        {
            using(var reader = command.ExecuteReader())
            {
               while(reader.Read())
               {
                   // your code here.
                   yield return reader;
               }
            }
        }
    }
}

İnsanlar bunun sezgisel olmadığını, yönteminizi çağıran kişilerin sonucu iki kez numaralandırmanın yöntemi iki kez arayacağını bilmeyebileceğini belirtti. İyi şanslar. Dil böyle çalışır. Gönderen sinyal budur IEnumerable<T>. Bu dönmez bir sebep yoktur List<T>ya T[]. Bunu bilmeyen insanlar eğitilmeli, çalışılmamalıdır.

Visual Studio'nun statik kod analizi adı verilen bir özelliği vardır. Kaynakları doğru bir şekilde bertaraf edip etmediğinizi öğrenmek için kullanabilirsiniz.


Dil, IEnumerabletamamen ilgisiz istisnalar atmak veya 5GB dosyalarını diske yazmak gibi her şeyi yapmanıza izin verir . Dilin buna izin vermesi, IEnumerablebunun gerçekleşeceğine dair hiçbir belirti vermeyen bir yöntemden bunu döndürmenin kabul edilebilir olduğu anlamına gelmez .
Ben Aaronson

1
Bir dönersek IEnumerable<T> olduğunu iki kez numaralandırılırken iki çağrıları neden olacağını göstergesidir. Numaralandırılabilir birden çok kez numaralandırırsanız, araçlarınızda bile yararlı uyarılar alırsınız. İstisnaları atmayla ilgili husus, iade türünden bağımsız olarak eşit derecede geçerlidir. Bu yöntem bir dönüş döndürürse List<T>aynı istisnaları atar.
nvoigt

Ne demek istediğimi anlıyorum ve bir dereceye kadar hemfikirim - size bir IEnumerableihtar imptoru veren bir yöntem kullanıyorsanız . Ama aynı zamanda bir yöntem yazarı olarak imzanızın ne anlama geldiğine uyma sorumluluğunuz olduğunu düşünüyorum. Yöntem çağrılır GetRecords, bu yüzden geri döndüğünde kayıtların olmasını beklersiniz . Eğer çağrıldıysa GiveMeSomethingICanPullRecordsFrom(ya da daha gerçekçi GetRecordSourceya da benzerse) bir tembel IEnumerabledaha kabul edilebilir olurdu.
Ben Aaronson

@ BenAaronson Katılıyorum örneğin File, ReadLinesve ReadAllLineskolayca ayrı söylenebilir ve bu iyi bir şey. (Her ne kadar bir yöntem aşırı yükünün yalnızca dönüş tipine göre değişememesi nedeniyle). Belki ReadRecords ve ReadAllRecords adlandırma şeması olarak da buraya sığar.
nvoigt

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.