2009-04-29 14 views
33

पृष्ठभूमि: मुझे स्ट्रिंग्स का एक गुच्छा मिला है जो मुझे डेटाबेस से मिल रहा है, और मैं उन्हें वापस करना चाहता हूं। परंपरागत रूप से, यह कुछ इस तरह होगा:सी # आईन्यूमेरेटर/उपज संरचना संभावित रूप से खराब है?

public List<string> GetStuff(string connectionString) 
{ 
    List<string> categoryList = new List<string>(); 
    using (SqlConnection sqlConnection = new SqlConnection(connectionString)) 
    { 
     string commandText = "GetStuff"; 
     using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)) 
     { 
      sqlCommand.CommandType = CommandType.StoredProcedure; 

      sqlConnection.Open(); 
      SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); 
      while (sqlDataReader.Read()) 
      { 
       categoryList.Add(sqlDataReader["myImportantColumn"].ToString()); 
      } 
     } 
    } 
    return categoryList; 
} 

लेकिन तब मैं समझ उपभोक्ता वस्तुओं के माध्यम से पुनरावृति करना चाहते करने जा रहा है और कुछ ज्यादा परवाह नहीं है, और मैं अपने आप में बॉक्स नहीं करना चाहते हैं एक सूची में, प्रति से, इसलिए यदि मैं एक आईनेमरेबल लौटाता हूं तो सबकुछ अच्छा/लचीला होता है। तो मैं इस तरह मैं एक "उपज वापसी" प्रकार डिजाइन का उपयोग इस संभाल करने के लिए कर सकता है सोच रहा था ... कुछ:

public IEnumerable<string> GetStuff(string connectionString) 
{ 
    using (SqlConnection sqlConnection = new SqlConnection(connectionString)) 
    { 
     string commandText = "GetStuff"; 
     using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)) 
     { 
      sqlCommand.CommandType = CommandType.StoredProcedure; 

      sqlConnection.Open(); 
      SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); 
      while (sqlDataReader.Read()) 
      { 
       yield return sqlDataReader["myImportantColumn"].ToString(); 
      } 
     } 
    } 
} 

लेकिन अब है कि मैं (उपज के बारे में थोड़ा और अधिक पढ़ रहा हूँ इस तरह साइटों पर .. .msdn इसका उल्लेख नहीं कर रहा था), यह जाहिरा तौर पर एक आलसी मूल्यांकनकर्ता है, जो अगले मूल्य के लिए पूछने वाले किसी की प्रत्याशा में, आसपास के पॉप्युलेटर की स्थिति को बनाए रखता है, और फिर इसे तब तक चलाता है जब तक वह अगले मूल्य को वापस न कर लेता है।

यह ज्यादातर मामलों में ठीक लगता है, लेकिन एक डीबी कॉल के साथ, यह थोड़ा पासा लगता है। कुछ हद तक प्रत्याशित उदाहरण के रूप में, यदि कोई उस से एक आईएनयूमेरेबल मांगता है, तो मैं डीबी कॉल से पॉप्युलेट कर रहा हूं, इसमें से आधे से गुजरता है, और फिर लूप में फंस जाता है ... जहां तक ​​मैं देख सकता हूं कि मेरा डीबी कनेक्शन जा रहा है हमेशा के लिए खुला रहने के लिए।

कुछ मामलों में परेशानी मांगने की तरह लगता है अगर इटरेटर खत्म नहीं होता है ... क्या मुझे कुछ याद आ रहा है?

+0

संपादित करने के लिए धन्यवाद, जॉन ... कि मैं उड़ पर टाइपिंग के लिए क्या मिलता है। – Beska

+1

जब तक आपका उपभोक्ता IENumerator पर 'निपटान' कहता है, तो आप सुरक्षित हैं। नीचे मेरी पोस्टिंग देखें। – tofi9

उत्तर

44

यह एक संतुलित कार्य है: क्या आप सभी डेटा को तुरंत स्मृति में मजबूर करना चाहते हैं ताकि आप कनेक्शन को मुक्त कर सकें, या आप डेटा स्ट्रीमिंग से लाभ प्राप्त करना चाहते हैं, सभी के लिए कनेक्शन जोड़ने की लागत पर पहर?

जिस तरह से मैं इसे देखता हूं, वह निर्णय संभावित रूप से कॉलर तक होना चाहिए, जो इस बारे में अधिक जानता है कि वे क्या करना चाहते हैं। आप एक iterator ब्लॉक का उपयोग कोड लिखते हैं, फोन करने वाले बहुत आसानी से बदल सकते हैं कि एक पूरी तरह से बफ़र प्रपत्र में प्रपत्र स्ट्रीमिंग:

List<string> stuff = new List<string>(GetStuff(connectionString)); 

, तो दूसरी ओर, आप अपने आप को बफरिंग है, कोई जिस तरह से कॉलर स्ट्रीमिंग मॉडल पर वापस जा सकता है।

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

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

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

+0

आपकी पसंद की पसंद सही है, लेकिन मुझे लगता है कि स्ट्रीमिंग न करने के निर्णय को डिफ़ॉल्ट करने के लिए अधिक वजन दिया जाना चाहिए। कनेक्शन या संसाधनों को छोड़कर स्केलेबिलिटी के मुद्दों का कारण बन जाएगा। डिफ़ॉल्ट व्यवहार सचेत होना चाहिए और समस्याओं का कारण नहीं होना चाहिए। –

8

आप कुछ भी याद नहीं कर रहे हैं। आपका नमूना दिखाता है कि उपज रिटर्न का उपयोग कैसे न करें। सूची में आइटम जोड़ें, कनेक्शन बंद करें, और सूची वापस करें। आपका विधि हस्ताक्षर अभी भी IENumerable लौटा सकता है।

संपादित करें: उस ने कहा, जॉन के पास एक बिंदु है (हैरान!): ऐसे दुर्लभ मौके हैं जहां स्ट्रीमिंग वास्तव में प्रदर्शन परिप्रेक्ष्य से सबसे अच्छी बात है। आखिरकार, यदि यह 100,000 (1,000,000? 10,000,000?) पंक्तियां हैं, तो हम यहां बात कर रहे हैं, आप इसे पहले स्मृति में लोड नहीं करना चाहते हैं।

+1

हाँ ... मैं केवल इसके अनुमानित पहलू को हाइलाइट कर रहा था क्योंकि यही कारण है कि मुझे पहले स्थान पर उपज का उपयोग करने के बारे में सोचना पड़ा। और उत्तर के लिए धन्यवाद ... यह देखकर खुशी हुई कि मैं गलत पेड़ को पूरी तरह से भौंकने वाला नहीं हूं। – Beska

+0

कोई चिंता नहीं दोस्त, मदद करने में खुशी हुई। अगर इसने आपके प्रश्न का उत्तर दिया है, तो इसे उत्तर के रूप में चिह्नित करना न भूलें ताकि यह अनुत्तरित प्रश्न सूची को छोड़ दे। –

+0

ओह, मैं हमेशा अपने प्रश्नों को हमेशा उत्तर के रूप में चिह्नित करता हूं ... लेकिन मैं इसे थोड़ा सा पकड़ना चाहता हूं, क्योंकि जॉन ने थोड़ा अलग दृष्टिकोण के साथ वजन कम किया है, और मैं देखना चाहता हूं कि यह कैसे खेलता है बाहर। – Beska

1

नहीं, आप सही रास्ते पर ... उपज पाठक को लॉक कर देंगे ... आप उसे किसी अन्य डेटाबेस कॉल करते हुए यहां IEnumerable

+0

देखें आप प्रदर्शन स्ट्रिंग में कई खुले SqlDataReaders को अनुमति देने के लिए कनेक्शन स्ट्रिंग में MARS सक्षम करते हैं। लेकिन फिर भी, इस पैटर्न में समस्याएं हैं। – spoulson

-2

न उपयोग उपज बुला परीक्षण कर सकते हैं कर रहे हैं। आपका नमूना ठीक है।

+0

एह? इस जवाब में क्या गलत था? –

0

मैंने इस दीवार में कुछ बार टक्कर लगी है। SQL डेटाबेस क्वेरी फ़ाइलों की तरह आसानी से स्ट्रीम करने योग्य नहीं हैं। इसके बजाए, केवल उतना ही पूछें जितना आपको लगता है कि आपको इसकी आवश्यकता होगी और जो भी कंटेनर आप चाहते हैं उसे वापस कर दें (IList<>, DataTable, आदि)। IEnumerable आपकी मदद नहीं करेगा।

-1

आप क्या कर सकते हैं इसके बजाय एक SqlDataAdapter का उपयोग करें और डेटाटेबल भरें। कुछ इस तरह:

public IEnumerable<string> GetStuff(string connectionString) 
{ 
    DataTable table = new DataTable(); 
    using (SqlConnection sqlConnection = new SqlConnection(connectionString)) 
    { 
     string commandText = "GetStuff"; 
     using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)) 
     { 
      sqlCommand.CommandType = CommandType.StoredProcedure; 
      SqlDataAdapter dataAdapter = new SqlDataAdapter(sqlCommand); 
      dataAdapter.Fill(table); 
     } 

    } 
    foreach(DataRow row in table.Rows) 
    { 
     yield return row["myImportantColumn"].ToString(); 
    } 
} 

इस तरह, आप एक शॉट में सब कुछ की क्वेरी रहे हैं, और तुरंत कनेक्शन बंद करने, अभी तक आप अभी भी lazily परिणाम पुनरावृत्ति कर रहे हैं। इसके अलावा, इस विधि के कॉलर परिणाम को किसी सूची में नहीं डाल सकते हैं और ऐसा कुछ नहीं कर सकते जो उन्हें नहीं करना चाहिए।

+3

मुझे समझ में नहीं आता कि इस परिणाम में "परिणामस्वरूप आलसी परिणाम" का क्या मतलब है। – mquander

+0

मुझे लगता है कि बिंदु यह है कि ओपी सूची <> (यही कारण है कि वह पहले स्थान पर उपज दृष्टिकोण के साथ गया), लेकिन साथ ही यह डेटाबेस कनेक्शन को खुला नहीं रखेगा। – Andy

+0

ठीक है, किसी भी दृष्टिकोण के साथ मुझे सूची <>; मैं IENumerable <> किसी भी तरह से वापस आ सकता है। मैं सिर्फ सूची <> से अधिक सामान्य की ओर बढ़ने के बारे में सोच रहा था, और यही वह था जो मुझे उपज के बारे में सोच रहा था, और इसके संभावित परिणाम। – Beska

10

आप हमेशा IENumerable से असुरक्षित नहीं हैं। यदि आप फ्रेमवर्क कॉल GetEnumerator छोड़ते हैं (जो अधिकांश लोग करेंगे), तो आप सुरक्षित हैं। मूल रूप से, आप अपने विधि का उपयोग कर कोड के carefullness के रूप में के रूप में सुरक्षित कर रहे हैं:

class Program 
{ 
    static void Main(string[] args) 
    { 
     // safe 
     var firstOnly = GetList().First(); 

     // safe 
     foreach (var item in GetList()) 
     { 
      if(item == "2") 
       break; 
     } 

     // safe 
     using (var enumerator = GetList().GetEnumerator()) 
     { 
      for (int i = 0; i < 2; i++) 
      { 
       enumerator.MoveNext(); 
      } 
     } 

     // unsafe 
     var enumerator2 = GetList().GetEnumerator(); 

     for (int i = 0; i < 2; i++) 
     { 
      enumerator2.MoveNext(); 
     } 
    } 

    static IEnumerable<string> GetList() 
    { 
     using (new Test()) 
     { 
      yield return "1"; 
      yield return "2"; 
      yield return "3"; 
     } 
    } 

} 

class Test : IDisposable 
{ 
    public void Dispose() 
    { 
     Console.WriteLine("dispose called"); 
    } 
} 

आप डेटाबेस कनेक्शन खुला है या नहीं छोड़ने के लिए बर्दाश्त कर सकते हैं या नहीं, साथ ही आपके वास्तुकला पर निर्भर करता है। यदि कॉलर लेनदेन में भाग लेता है (और आपका कनेक्शन स्वतः सूचीबद्ध है), तो कनेक्शन को फ्रेमवर्क द्वारा वैसे भी खुला रखा जाएगा।

yield का एक और लाभ (सर्वर-साइड कर्सर का उपयोग करते समय) है, तो आपके कोड को डेटाबेस से सभी डेटा (उदाहरण: 1,000 आइटम) को पढ़ने की ज़रूरत नहीं है, अगर आपका उपभोक्ता पहले लूप से बाहर निकलना चाहता है (उदाहरण: 10 वीं वस्तु के बाद)। यह पूछताछ डेटा तेज कर सकते हैं। खासकर ओरेकल पर्यावरण में, जहां सर्वर-साइड कर्सर डेटा पुनर्प्राप्त करने का सामान्य तरीका है।

+3

+1 निपटान के बारे में विवरण के लिए +1, लेकिन मुझे नहीं लगता कि यह चिंता थी - मैं * विश्वास करता हूं * बेस्का कॉलर के लूप के कुछ पुनरावृत्तियों के बारे में चिंतित है, जो प्रक्रिया के लिए बहुत लंबा समय ले रहा है, डेटाबेस कनेक्शन को तब खुला रहता है जब यह ' टी वास्तव में जरूरत है। –

+0

धन्यवाद, कनेक्शन को खोलने के बारे में मेरी दृष्टि से अपडेट किया गया। – tofi9

1

कॉलर इस समस्या का कारण बनने का एकमात्र तरीका है यदि कॉलर IEnumerable<T> के प्रोटोकॉल का दुरुपयोग करता है। इसका उपयोग करने का सही तरीका उस पर Dispose पर कॉल करना है जब इसकी आवश्यकता नहीं है।

कार्यान्वयन yield return द्वारा उत्पन्न एक संकेत किसी भी खुले finally ब्लॉक निष्पादित करने के लिए है, जो अपने उदाहरण में वस्तुओं आप using बयान में बना लिया है पर Dispose कॉल करेंगे के रूप में Dispose कॉल लेता है।

कई भाषा विशेषताएं हैं (विशेष रूप से foreach) जो IEnumerable<T> का उपयोग करने में बहुत आसान बनाती हैं।

+0

यदि आप उपज रिटर्न कीवर्ड के माध्यम से कार्यान्वित एन्युमरेटर्स द्वारा/उसके भीतर निपटान का उपयोग करने के बारे में कुछ दस्तावेज साइट पर जा सकते हैं तो यह सहायक होगा। – jpierson

6

के रूप में एक अलग रूप में - ध्यान दें कि IEnumerable<T> दृष्टिकोण अनिवार्य रूप से क्या LINQ प्रदाताओं (LINQ करने वाली एसक्यूएल, LINQ करने वाली संस्थाओं) जीने के लिए करना है। जैसा कि जॉन कहते हैं, दृष्टिकोण के फायदे हैं। हालांकि, निश्चित समस्याएं भी हैं - विशेष रूप से (मेरे लिए) अलगाव के संयोजन के संदर्भ में अमूर्त।

क्या मैं यहाँ का मतलब है कि है:

    एक MVC परिदृश्य में
  • (उदाहरण के लिए) आप चाहते हैं कि आपके ताकि आप इसे पर काम करता है परीक्षण कर सकते हैं, करने के लिए कदम वास्तव में डेटा मिल "डेटा प्राप्त" नियंत्रक, नहीं दृश्य
  • आप गारंटी नहीं दे सकते कि एक और दाल कार्यान्वयन सक्षम डेटा स्ट्रीम करने के लिए (उदाहरण के लिए, एक POX/WSE/सोप कॉल कर सकते हैं हो सकता है (बिना .ToList() कॉल करने के लिए आदि याद करने के लिए) हम नहीं ¢ स्ट्रीम रिकॉर्ड); और आप नहीं है जरूरी व्यवहार भ्रमित करने वाले अलग बनाना चाहते हैं (यानी कनेक्शन अभी भी एक कार्यान्वयन के साथ यात्रा के दौरान खुला है, और किसी अन्य के लिए बंद कर दिया) मेरे विचार यहाँ से कुछ ही देर में

इस संबंध: Pragmatic LINQ

लेकिन मुझे तनाव देना चाहिए - स्ट्रीमिंग बेहद वांछनीय होने पर निश्चित रूप से समय होते हैं। यह एक साधारण "हमेशा बनाम कभी नहीं" चीज नहीं है ...

0

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

एक विकल्प के रूप में, आप उपयोगकर्ता को बुलियन में पास करने के लिए मजबूर कर सकते हैं यह इंगित करने के लिए कि डेटा को बफर किया जाना चाहिए या नहीं। यदि सही है, तो डेटा buffered है और कनेक्शन जल्द से जल्द बंद कर दिया गया है। यदि गलत है, तो डेटा buffered नहीं है और डेटाबेस कनेक्शन तब तक खुला रहता है जब तक उपयोगकर्ता को इसकी आवश्यकता होती है। एक बूलियन पैरामीटर होने से उपयोगकर्ता को पसंद करने के लिए मजबूर किया जाता है, जिससे यह सुनिश्चित होता है कि वे इस मुद्दे के बारे में जानते हैं।

3

थोड़ा अधिक संक्षिप्त इटरेटर के मूल्यांकन के लिए मजबूर करने के लिए रास्ता:

using System.Linq; 

//... 

var stuff = GetStuff(connectionString).ToList(); 
संबंधित मुद्दे