क्या हम डेटाबेस कनेक्शन को बंद कर देंगे, अगर हम डेटारीडर पंक्ति को प्राप्त करते हैं और सभी रिकॉर्ड नहीं पढ़ते हैं?


15

यह समझने के लिए कि yieldकीवर्ड कैसे काम करता है, मैं StackOverflow पर link1 और link2 के पार आया, जो yield returnDataReader पर पुनरावृत्ति करते हुए इसके उपयोग की वकालत करता है और यह मेरी आवश्यकता के अनुरूप भी है। लेकिन यह मुझे आश्चर्यचकित करता है कि क्या होता है, अगर मैं yield returnनीचे दिखाए गए अनुसार उपयोग करता हूं और अगर मैं पूरे डेटारीडर के माध्यम से पुनरावृति नहीं करता हूं, तो क्या डीबी कनेक्शन हमेशा के लिए खुला रहेगा?

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

मैंने एक नमूना कंसोल ऐप में एक ही उदाहरण की कोशिश की और डिबगिंग करते समय देखा कि आखिरकार ब्लॉक GetRecords()निष्पादित नहीं किया गया है। मैं यह सुनिश्चित कैसे कर सकता हूं कि DB कनेक्शन बंद हो गया है? क्या yieldकीवर्ड का उपयोग करने से बेहतर तरीका है ? मैं एक कस्टम क्लास डिजाइन करने की कोशिश कर रहा हूं जो चुनिंदा एसक्यूएल और डीबी पर संग्रहीत प्रक्रियाओं को निष्पादित करने के लिए जिम्मेदार होगा और परिणाम लौटाएगा। लेकिन मैं Caller को DataReader वापस नहीं करना चाहता। इसके अलावा, मैं यह सुनिश्चित करना चाहता हूं कि कनेक्शन सभी परिदृश्यों में बंद हो जाएगा।

संपादित करें बेन के उत्तर के उत्तर को बदल दें क्योंकि विधि कॉलर्स से विधि का सही तरीके से उपयोग करने की अपेक्षा करना गलत है और डीबी कनेक्शन के संबंध में यह अधिक महंगा होगा यदि विधि को बिना किसी कारण के कई बार कहा जाता है।

विस्तृत विवरण के लिए धन्यवाद जैकब और बेन।


मैं जो देख रहा हूं उससे आप पहली बार में संबंध नहीं खोल रहे हैं
paparazzo

@ फ्रिसबी एडिट्ड :)
गावडेप्रसाद

जवाबों:


12

हां, आप उस समस्या का सामना करेंगे जिसका आप वर्णन करते हैं: जब तक आप परिणाम पर पुनरावृत्ति समाप्त नहीं करते, तब तक आप कनेक्शन खुला रखेंगे। इससे निपटने के लिए मैं सोच सकता हूँ कि दो सामान्य दृष्टिकोण हैं:

धक्का, मत खींचो

वर्तमान में आप एक IEnumerable<IDataRecord>, एक डेटा संरचना लौटा रहे हैं जिससे आप खींच सकते हैं। इसके बजाय, आप अपने परिणामों को बाहर धकेलने के लिए अपना तरीका बदल सकते हैं। सबसे आसान तरीका यह होगा Action<IDataRecord>कि जिसे प्रत्येक पुनरावृत्ति पर बुलाया जाए:

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

ध्यान दें कि आप आइटमों के संग्रह के साथ काम कर रहे हैं, IObservable/ IObserverकुछ अधिक उपयुक्त डेटा संरचना हो सकती है, लेकिन जब तक आपको इसकी आवश्यकता नहीं होती है, एक सरल Actionबहुत सरल है।

बेसब्री से मूल्यांकन

एक विकल्प सिर्फ यह सुनिश्चित करने के लिए है कि वापसी से पहले पुनरावृति पूरी तरह से पूरी हो जाए।

आप सामान्य रूप से केवल एक सूची में परिणाम डालकर ऐसा कर सकते हैं, फिर वापस लौट सकते हैं, लेकिन इस मामले में प्रत्येक आइटम की अतिरिक्त जटिलता पाठक के लिए एक ही संदर्भ है। इसलिए आपको पाठक से जो परिणाम चाहिए, उसे निकालने के लिए कुछ चाहिए:

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

मैं उस दृष्टिकोण का उपयोग कर रहा हूं जिसका नाम आपने ईगरली इवैल्यूएशन है। यदि मेरी SQLCommand का डाट्रेडर कई आउटपुट (जैसे myReader.NextResult ()) और मैं सूची का निर्माण करता है तो यह एक मेमोरी ओवरहेड होगा? या फोन करने वाले को डेटाएडर भेजना [मैं व्यक्तिगत रूप से इसे पसंद नहीं करता हूं] बहुत कुशल होगा? [लेकिन कनेक्शन लंबे समय तक खुला रहेगा]। सूची की एक सूची बनाने की तुलना में कनेक्शन को खुले रखने के व्यापार के बीच मैं यहां ठीक से भ्रमित हूं। आप सुरक्षित रूप से मान सकते हैं कि मेरे वेबपेज पर 500 से करीब लोग आएंगे जो डीबी से जुड़ते हैं।
गावडेप्रसाद

1
@ गावडेप्रसाद यह निर्भर करता है कि कॉलर पाठक के साथ क्या करेगा। यदि यह केवल एक संग्रह में आइटम डालते हैं, तो कोई महत्वपूर्ण ओवरहेड नहीं है। यदि यह एक ऐसा ऑपरेशन करेगा जो मेमोरी में पूरे बहुत सारे मान रखने की आवश्यकता नहीं है, तो एक मेमोरी ओवरहेड है। हालाँकि, जब तक आप बहुत बड़ी मात्रा में भंडारण नहीं करते हैं, तब तक यह संभव नहीं है कि आपको अधिक देखभाल करनी चाहिए। किसी भी तरह से, एक महत्वपूर्ण समय ओवरहेड नहीं होना चाहिए।
बेन आरोनसन

"पुश, पुल न करें" दृष्टिकोण कैसे हल करता है "जब तक आप परिणाम पर पुनरावृत्ति समाप्त नहीं करते, आप कनेक्शन खुला रखेंगे" समस्या? कनेक्शन तब भी खुला रखा जाता है जब तक आप इस दृष्टिकोण के साथ भी इसे समाप्त नहीं करते हैं, है न?
बोर्नटकोड 15

1
@BornToCode प्रश्न देखें: "यदि मैं नीचे दिखाए गए अनुसार उपज प्रतिफल का उपयोग करता हूं और यदि मैं पूरे DataReader के माध्यम से पुनरावृति नहीं करता, तो क्या DB कनेक्शन हमेशा खुला रहेगा"। समस्या यह है कि आप एक IEnumerable लौटाते हैं और हर कॉल करने वाला जो इसे संभालता है उसे इस विशेष गोच के बारे में पता होना चाहिए: "यदि आप मेरे ऊपर पुनरावृत्ति नहीं करते हैं, तो मैं एक कनेक्शन खोलूंगा"। पुश विधि में, आप उस जिम्मेदारी को कॉलर से दूर ले जाते हैं- उनके पास आंशिक रूप से पुनरावृति करने का कोई विकल्प नहीं होता है। जब तक नियंत्रण उन्हें वापस नहीं किया जाता, तब तक कनेक्शन बंद रहता है।
बेन आरोनसन

1
यह एक उत्कृष्ट उत्तर है। मुझे पुश अप्रोच पसंद है, क्योंकि यदि आपके पास एक बड़ी क्वेरी है जिसे आप एक पृष्ठांकित फैशन में प्रस्तुत कर रहे हैं, तो आप एक्शन <> के बजाय फंक <> में पास करके गति के लिए पहले पढ़े गए को रद्द कर सकते हैं; यह GetRecords फ़ंक्शन पेजिंग करने की तुलना में क्लीनर लगता है।
Whelkaholism

10

आपका finallyब्लॉक हमेशा निष्पादित होगा।

जब आप yield returnसंकलक का उपयोग करते हैं तो एक राज्य मशीन को लागू करने के लिए एक नया नेस्टेड वर्ग बनाएगा।

इस वर्ग finallyमें अलग-अलग विधियों के रूप में ब्लॉक से सभी कोड होंगे । यह उन finallyब्लॉकों का ट्रैक रखेगा, जिन्हें राज्य के आधार पर निष्पादित करने की आवश्यकता है। सभी आवश्यक finallyब्लॉकों को Disposeविधि में निष्पादित किया जाएगा ।

C # भाषा विनिर्देश के अनुसार, foreach (V v in x) embedded-statementइसका विस्तार किया जाता है

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

इसलिए प्रगणक भले ही आप के साथ पाश से बाहर निकलने के निपटारा किया जाएगा breakया return

पुनरावृत्ति कार्यान्वयन के बारे में अधिक जानकारी प्राप्त करने के लिए आप जॉन स्कीट के इस लेख को पढ़ सकते हैं

संपादित करें

इस तरह के दृष्टिकोण के साथ समस्या यह है कि आप अपने वर्ग के ग्राहकों पर सही तरीके से उपयोग करने के लिए भरोसा करते हैं। वे उदाहरण के लिए, एन्यूमरेटर को सीधे प्राप्त कर सकते हैं और इसे एक whileलूप में बिना डिस्पोज़ किए इसके माध्यम से पुनरावृत्त कर सकते हैं।

आपको @BenAaronson द्वारा सुझाए गए समाधानों में से एक पर विचार करना चाहिए।


1
यह बेहद अविश्वसनीय है। उदाहरण के लिए: var records = GetRecords(); var first = records.First(); var count = records.Count()वास्तव में उस विधि को दो बार क्रियान्वित करेंगे, हर बार कनेक्शन और रीडर को खोलना और बंद करना। दो बार इस पर फेरबदल करना एक ही काम करता है। आप वास्तव में इस पर पुनरावृत्ति करने से पहले लंबे समय तक परिणाम भी पास कर सकते हैं, आदि। विधि हस्ताक्षर में कुछ भी इस तरह का व्यवहार नहीं करता है
बेन आरोनसन

2
@BenAaronson मैंने अपने उत्तर में वर्तमान कार्यान्वयन के बारे में विशिष्ट प्रश्न पर ध्यान केंद्रित किया। यदि आप पूरी समस्या को देखते हैं, तो मैं मानता हूं कि आपके उत्तर में आपके द्वारा सुझाए गए तरीके का उपयोग करना बेहतर है।
जैकब लोर्ट्ज़

1
@ जाकुब्लार्ट मेला पर्याप्त
बेन

2
@EsbenSkovPedersen आमतौर पर जब आप कुछ बेवकूफ-सबूत बनाने की कोशिश करते हैं, तो आप कुछ कार्यक्षमता खो देते हैं। आपको इसे संतुलित करने की आवश्यकता है। यहां हम परिणामों को उत्सुकता से लोड करके नहीं खोते हैं। वैसे भी मेरे लिए सबसे बड़ी समस्या नामकरण की है। जब मुझे GetRecordsलौटने का एक तरीका दिखाई देता है IEnumerable, तो मैं उम्मीद करता हूं कि यह रिकॉर्ड को मेमोरी में लोड कर देगा। दूसरी ओर, अगर यह एक संपत्ति थी Records, तो मैं यह मानूंगा कि यह डेटाबेस पर किसी प्रकार का अमूर्त है।
जैकब लोर्ट्ज़

1
@EsbenSkovPedersen खैर, nvoigt के जवाब पर मेरी टिप्पणी देखें। मुझे IEnumerable लौटने वाले तरीकों से सामान्य रूप से कोई समस्या नहीं है जो कि जब यह अधिक महत्वपूर्ण काम करेगा, लेकिन इस मामले में यह विधि हस्ताक्षर के निहितार्थ के अनुरूप नहीं लगता है
बेन आरोनसन

5

विशिष्ट yieldव्यवहार के बावजूद , आपके कोड में निष्पादन पथ होते हैं जो आपके संसाधनों को सही ढंग से निपटान नहीं करेंगे। क्या होगा यदि आपकी दूसरी पंक्ति एक अपवाद या आपके तीसरे को फेंकती है? या आपका चौथा भी? आपको बहुत जटिल कोशिश / अंत में श्रृंखला की आवश्यकता होगी, या आप usingब्लॉकों का उपयोग कर सकते हैं ।

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

लोगों ने टिप्पणी की है कि यह सहज ज्ञान युक्त नहीं है, कि आपकी विधि को कॉल करने वाले लोग यह नहीं जानते होंगे कि परिणाम की गणना करने वाले दो बार विधि को दो बार कहेंगे। खैर, कठिन भाग्य। जिस तरह से भाषा काम करती है। जो संकेत IEnumerable<T>भेजता है। एक कारण है कि यह वापस नहीं आता है List<T>या नहीं T[]। जिन लोगों को यह पता नहीं है कि उन्हें शिक्षित होने की जरूरत है, आसपास काम नहीं किया।

विजुअल स्टूडियो में स्थिर कोड विश्लेषण नामक एक सुविधा होती है। आप यह पता लगाने के लिए इसका उपयोग कर सकते हैं कि क्या आपने संसाधनों का सही तरीके से निपटान किया है।


भाषा IEnumerableसभी प्रकार की चीजों को करने की अनुमति देती है , जैसे कि पूरी तरह से असंबंधित अपवादों को फेंकना या डिस्क पर 5GB फाइलें लिखना। सिर्फ इसलिए कि भाषा इसकी अनुमति देती है इसका मतलब यह नहीं है कि इसे वापस करने के लिए स्वीकार्य IEnumerableहै यह एक विधि से करता है जो कोई संकेत नहीं देता है कि ऐसा होगा।
बेन आरोनसन

1
एक रिटर्निंग IEnumerable<T> है संकेत है कि इसे दो बार की गणना दो कॉल का परिणाम देगा। यदि आप कई बार गणना करने योग्य हैं, तो आपको अपने औजारों में सहायक चेतावनी भी मिलेगी। अपवाद के बारे में बात वापसी के प्रकार की परवाह किए बिना समान रूप से मान्य है। अगर वह तरीका वापस आ जाता है तो List<T>यह उसी अपवाद को फेंक देगा।
nvoigt

मुझे आपकी बात समझ में आ रही है और कुछ हद तक मैं सहमत हूँ- यदि आप एक ऐसी विधि का उपभोग कर रहे हैं जो आपको IEnumerableतत्कालीन कैवेट एम्प्टर देती है। लेकिन मुझे यह भी लगता है कि एक विधि लेखक के रूप में, आपके पास अपने हस्ताक्षर का पालन करने की जिम्मेदारी है। विधि को कहा जाता है GetRecords, इसलिए जब यह वापस आता है तो आपको इसकी उम्मीद होगी, आपको पता है, रिकॉर्ड प्राप्त किया है । यदि यह GiveMeSomethingICanPullRecordsFrom(या, अधिक वास्तविक GetRecordSourceया समान) कहा जाता था , तो एक आलसी IEnumerableअधिक स्वीकार्य होगा।
बेन आरोनसन

@BenAaronson मैं सहमत हूं कि उदाहरण के लिए File, ReadLinesऔर ReadAllLinesआसानी से अलग किया जा सकता है और यह एक अच्छी बात है। (हालांकि यह इस तथ्य के कारण अधिक है कि एक विधि अधिभार केवल वापसी प्रकार से भिन्न नहीं हो सकता है)। शायद ReadRecords और ReadAllRecords नामकरण योजना के रूप में यहाँ भी फिट होगा।
नवगीत

2

आपने जो किया वह गलत लगता है, लेकिन ठीक काम करेगा।

आपको एक IEnumerator <> का उपयोग करना चाहिए , क्योंकि यह आईडीआईसोपायरी भी विरासत में मिला है ।

लेकिन क्योंकि आप एक फॉर्च्यूनर का उपयोग कर रहे हैं, इसलिए कंपाइलर अभी भी आईरिसॉमीटर का उपयोग कर रहा है ताकि फॉर्च्यूनर के लिए IEnumerator <> जेनरेट किया जा सके।

वास्तव में, फॉर्च्यूनर में बहुत सारी चीजें शामिल होती हैं।

Https://stackoverflow.com/questions/13459447/do-i-need-to-consider-disposing-of-any-ienumerablet-i-use चेक करें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.