.ToList (), .AsEnumerable (), AsQueryable () के बीच क्या अंतर है?


182

मैं LINQ से एंटिटीज और LINQ के कुछ अंतर को उन ऑब्जेक्ट्स के लिए जानता हूँ जो पहली इम्प्लीमेंट्स IQueryableऔर दूसरा इम्प्लीमेंट्स IEnumerableऔर मेरा प्रश्न स्कोप EF 5 के भीतर है।

मेरा सवाल यह है कि उन 3 विधियों में तकनीकी अंतर क्या है? मैं देखता हूं कि कई स्थितियों में ये सभी काम करते हैं। मैं भी उनके जैसे संयोजनों का उपयोग करते हुए देखता हूं .ToList().AsQueryable()

  1. उन तरीकों का क्या मतलब है, बिल्कुल?

  2. क्या कोई प्रदर्शन मुद्दा या कुछ ऐसा है जो एक के बाद एक का उपयोग करेगा?

  3. क्यों किसी एक का उपयोग होता है, उदाहरण के लिए, .ToList().AsQueryable()के बजाय .AsQueryable()?


जवाबों:


354

इस बारे में बहुत कुछ कहा जा सकता है। मुझ पर ध्यान केंद्रित करते हैं AsEnumerableऔर AsQueryableऔर उल्लेख ToList()रास्ते।

ये तरीके क्या करते हैं?

AsEnumerableऔर क्रमशः AsQueryableकास्ट या कन्वर्ट IEnumerableया IQueryable। मैं कहता हूं कि एक कारण के साथ कास्ट या कन्वर्ट करें:

  • जब स्रोत ऑब्जेक्ट पहले से ही लक्ष्य इंटरफ़ेस को लागू करता है, तो स्रोत ऑब्जेक्ट स्वयं ही वापस आ जाता है, लेकिन लक्ष्य इंटरफ़ेस में डाला जाता है। दूसरे शब्दों में: प्रकार नहीं बदला गया है, लेकिन संकलन-समय प्रकार है।

  • जब स्रोत ऑब्जेक्ट लक्ष्य इंटरफ़ेस को लागू नहीं करता है, तो स्रोत ऑब्जेक्ट एक ऑब्जेक्ट में परिवर्तित हो जाता है जो लक्ष्य इंटरफ़ेस को लागू करता है। तो प्रकार और संकलन-समय दोनों प्रकार बदल दिए जाते हैं।

इसे मैं कुछ उदाहरणों के साथ दिखाता हूं। मुझे यह छोटा सा तरीका मिल गया है जो संकलन-समय प्रकार और एक वस्तु के वास्तविक प्रकार की रिपोर्ट करता है ( सौजन्य जॉन स्कीट ):

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

आइए एक मनमाना linq-to-sql आज़माएं Table<T>, जो लागू होता है IQueryable:

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());

परिणाम:

Compile-time type: Table`1
Actual type: Table`1

Compile-time type: IEnumerable`1
Actual type: Table`1

Compile-time type: IQueryable`1
Actual type: Table`1

आप देखते हैं कि तालिका का वर्ग हमेशा ही वापस आ जाता है, लेकिन इसका प्रतिनिधित्व बदल जाता है।

अब एक वस्तु जो लागू होती है IEnumerable, नहीं IQueryable:

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());

परिणाम:

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1

वो रहा। AsQueryable()सरणी को एक में परिवर्तित कर दिया है EnumerableQuery, जो " डेटा स्रोत के IEnumerable<T>रूप में एक संग्रह का प्रतिनिधित्व करता है IQueryable<T>।" (MSDN)।

क्या फायदा?

AsEnumerableअक्सर किसी भी IQueryableकार्यान्वयन से LINQ को ऑब्जेक्ट्स (L2O) पर स्विच करने के लिए उपयोग किया जाता है, ज्यादातर इसलिए क्योंकि पूर्व L2O के कार्यों का समर्थन नहीं करता है। अधिक जानकारी के लिए देखें LINQ इकाई पर AsEnumerable () का क्या प्रभाव है?

उदाहरण के लिए, एक एंटिटी फ्रेमवर्क क्वेरी में हम केवल एक सीमित संख्या में विधियों का उपयोग कर सकते हैं। इसलिए यदि, उदाहरण के लिए, हमें अपने स्वयं के तरीकों में से एक का उपयोग करने की आवश्यकता है, तो हम आमतौर पर ऐसा कुछ लिखेंगे

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList - जो IEnumerable<T>कि a को a में परिवर्तित करता है List<T>- अक्सर इस उद्देश्य के लिए भी उपयोग किया जाता है। AsEnumerableबनाम का उपयोग करने का लाभ यह ToListहै कि AsEnumerableक्वेरी निष्पादित नहीं करता है। AsEnumerableआस्थगित निष्पादन को संरक्षित करता है और अक्सर बेकार मध्यवर्ती सूची का निर्माण नहीं करता है।

दूसरी ओर, जब LINQ क्वेरी का जबरन निष्पादन वांछित होता है, ToListतो ऐसा करने का एक तरीका हो सकता है।

AsQueryableLINQ स्टेटमेंट में एक संग्रहणीय संग्रह स्वीकार अभिव्यक्ति बनाने के लिए इस्तेमाल किया जा सकता है। अधिक विवरण के लिए यहां देखें: क्या मुझे वास्तव में संग्रह पर AsQueryable () का उपयोग करने की आवश्यकता है?

मादक द्रव्यों के सेवन पर ध्यान दें!

AsEnumerableदवा की तरह काम करता है। यह एक त्वरित सुधार है, लेकिन एक लागत पर और यह अंतर्निहित समस्या का समाधान नहीं करता है।

कई स्टैक ओवरफ्लो उत्तरों में, मैं लोगों AsEnumerableको LINQ अभिव्यक्तियों में असमर्थित तरीकों के साथ किसी भी समस्या को ठीक करने के लिए आवेदन करने के लिए देखता हूं । लेकिन कीमत हमेशा स्पष्ट नहीं होती है। उदाहरण के लिए, यदि आप ऐसा करते हैं:

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

... सब कुछ बड़े करीने से SQL कथन में फ़िल्टर किया गया है ( Where) और प्रोजेक्ट ( Select)। यानी, SQL परिणाम सेट की लंबाई और चौड़ाई, क्रमशः, दोनों कम हो जाते हैं।

अब मान लें कि उपयोगकर्ता केवल तारीख का भाग देखना चाहते हैं CreateDate। एंटिटी फ्रेमवर्क में आपको जल्दी पता चलेगा कि ...

.Select(x => new { x.Name, x.CreateDate.Date })

... समर्थित नहीं है (लेखन के समय)। आह, सौभाग्य से वहाँ AsEnumerableतय है:

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

ज़रूर चलता है, शायद। लेकिन यह पूरी तालिका को स्मृति में खींचता है और फिर फ़िल्टर और अनुमानों को लागू करता है। ठीक है, ज्यादातर लोग Whereपहले करने के लिए काफी स्मार्ट हैं :

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })

लेकिन फिर भी सभी कॉलम पहले प्राप्त किए जाते हैं और प्रक्षेपण स्मृति में किया जाता है।

असली तय यह है:

context.MyLongWideTable
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })

(लेकिन इसके लिए बस थोड़ा सा और ज्ञान चाहिए ...)

ये तरीके क्या नहीं करते हैं?

IQueryable क्षमताओं को पुनर्स्थापित करें

अब एक महत्वपूर्ण चेतावनी। जब तुम करोगे

context.Observations.AsEnumerable()
                    .AsQueryable()

आप के रूप में प्रतिनिधित्व स्रोत वस्तु के साथ समाप्त होगा IQueryable। (क्योंकि दोनों विधियाँ केवल डाली जाती हैं और परिवर्तित नहीं होती हैं)।

लेकिन जब आप करते हैं

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()

परिणाम क्या होगा?

Selectएक का उत्पादन WhereSelectEnumerableIterator। यह एक आंतरिक .Net वर्ग है जो लागू होता है IEnumerable, नहींIQueryable । इसलिए दूसरे प्रकार में रूपांतरण हुआ है और बाद में AsQueryableकभी भी मूल स्रोत वापस नहीं आ सकता है।

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

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

जहां स्थिति एसक्यूएल में अनुवाद नहीं किया जाएगा। AsEnumerable()LINQ कथनों के बाद निश्चित रूप से एंटिटी फ्रेमवर्क क्वेरी प्रदाता के साथ कनेक्शन काट देता है।

मैं जानबूझकर इस उदाहरण को दिखाता हूं क्योंकि मैंने यहां ऐसे प्रश्न देखे हैं जहां उदाहरण के लिए लोग Includeकॉल करके संग्रह में 'इंजेक्शन' लगाने की कोशिश करते हैं AsQueryable। यह संकलित करता है और चलाता है, लेकिन यह कुछ भी नहीं करता है क्योंकि अंतर्निहित वस्तु का Includeअब कार्यान्वयन नहीं है।

निष्पादित

दोनों AsQueryableऔर AsEnumerableनिष्पादित नहीं है (या गणना ) स्रोत ऑब्जेक्ट। वे केवल अपने प्रकार या प्रतिनिधित्व को बदलते हैं। दोनों में इंटरफेस था, IQueryableऔर IEnumerable, "होने के लिए इंतजार कर रहे एक गणना" के अलावा कुछ भी नहीं है। इससे पहले कि वे ऐसा करने के लिए मजबूर कर रहे हैं, उदाहरण के लिए, कॉल करके, उन्हें निष्पादित नहीं किया जाता है ToList()

इसका मतलब है कि किसी ऑब्जेक्ट पर IEnumerableकॉल करके प्राप्त किया गया निष्पादन, अंतर्निहित निष्पादित करेगा । वसीयत का एक बाद का निष्पादन फिर से निष्पादित करेगा । जो बहुत महंगा हो सकता है।AsEnumerableIQueryableIQueryableIEnumerableIQueryable

विशिष्ट कार्यान्वयन

अब तक, यह केवल Queryable.AsQueryableऔर Enumerable.AsEnumerableविस्तार विधियों के बारे में था । लेकिन निश्चित रूप से कोई भी एक ही नाम (और कार्यों) के साथ उदाहरण के तरीकों या विस्तार विधियों को लिख सकता है।

वास्तव में, एक विशिष्ट AsEnumerableविस्तार विधि का एक सामान्य उदाहरण है DataTableExtensions.AsEnumerableDataTableलागू नहीं करता है IQueryableया IEnumerable, इसलिए नियमित विस्तार विधियाँ लागू नहीं होती हैं।


उत्तर के लिए धन्यवाद, क्या आप ओपी के 3 प्रश्न के लिए अपना जवाब साझा कर सकते हैं - 3. उदाहरण के लिए, .ToList ()। AsQueryable () के बजाय .AsQueryable () का उपयोग क्यों करेगा? ?
किग्ज़देव

@ विक्रम मैं ऐसा कुछ भी नहीं सोच सकता जहाँ यह उपयोगी होगा। जैसा कि मैंने समझाया, आवेदन AsQueryable()अक्सर गलत धारणा पर आधारित होता है। लेकिन मैं थोड़ी देर के लिए अपने सिर के पीछे के हिस्से को उबालने देता हूं और देखता हूं कि क्या मैं उस प्रश्न पर कुछ और कवरेज जोड़ सकता हूं।
गर्ट अर्नोल्ड

1
बहुत बढ़िया जवाब। क्या आप सटीक रूप से बता सकते हैं कि क्या होता है यदि IEnumerable को एक IQueryable पर AsEnumerable () कहकर प्राप्त किया जाता है, तो कई बार गणना की जाती है? क्या क्वेरी को कई बार निष्पादित किया जाएगा, या डेटाबेस में पहले से लोड किए गए डेटा को मेमोरी में पुन: उपयोग किया जाएगा?
एंटोनिनोड

@antoninod अच्छा विचार है। किया हुआ।
गर्ट अर्नोल्ड

46

सूची बनाने के लिए()

  • तुरंत क्वेरी निष्पादित करें

AsEnumerable ()

  • आलसी (क्वेरी को बाद में निष्पादित करें)
  • पैरामीटर: Func<TSource, bool>
  • हर बार एप्लिकेशन मेमोरी में लोड करें , और फिर उन्हें संभाल / फ़िल्टर करें। (उदा। कहाँ / ले / छोड़ें, यह मेमोरी में तालिका 1 से * का चयन करेगा, फिर पहले X तत्वों का चयन करेगा) (इस मामले में, यह क्या किया: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • आलसी (क्वेरी को बाद में निष्पादित करें)
  • पैरामीटर: Expression<Func<TSource, bool>>
  • एक्सप्रेशन को टी-एसक्यूएल में (विशिष्ट प्रदाता के साथ), दूर से क्वेरी करें और परिणाम को अपनी एप्लिकेशन मेमोरी में लोड करें।
  • यही कारण है कि DbSet (एंटिटी फ्रेमवर्क में) भी कुशल क्वेरी प्राप्त करने के लिए IQueryable विरासत में मिला है।
  • प्रत्येक रिकॉर्ड को लोड न करें, जैसे कि टेक (5), यह पृष्ठभूमि में चुनिंदा शीर्ष 5 * एसक्यूएल उत्पन्न करेगा। इसका मतलब है कि यह प्रकार SQL डेटाबेस के लिए अधिक अनुकूल है, और यही कारण है कि इस प्रकार का आमतौर पर उच्च प्रदर्शन होता है और डेटाबेस से निपटने के दौरान इसकी सिफारिश की जाती है।
  • इसलिए AsQueryable()आमतौर पर बहुत तेजी से काम करता है AsEnumerable()क्योंकि यह पहली बार में टी-एसक्यूएल उत्पन्न करता है, जिसमें आपके लिनक में सभी स्थितियां शामिल हैं।

14

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

इसके अलावा, इस उत्तर को देखें कि सूची () के बजाय AsQueryable () का उपयोग क्यों करें?

EDIT: इसके अलावा, आपके मामले में एक बार जब आप ToList () करते हैं तो प्रत्येक बाद का ऑपरेशन AsQueryable () सहित स्थानीय होता है। एक बार स्थानीय स्तर पर क्रियान्वयन शुरू करने के बाद आप रिमोट पर स्विच नहीं कर सकते। आशा है कि यह थोड़ा और अधिक स्पष्ट करता है।


2
"AsQueryable () दूरस्थ रूप से सब कुछ निष्पादित करेगा" केवल अगर गणना करने योग्य पहले से ही क्वेरी योग्य है। अन्यथा, यह संभव नहीं है, और सब कुछ अभी भी स्थानीय रूप से चलता है। प्रश्न में ".... ToList ()। AsQueryable ()" है, जो आपके उत्तर में कुछ स्पष्टीकरण का उपयोग कर सकता है, IMO।

2

नीचे दिए गए कोड पर खराब प्रदर्शन का सामना करना पड़ा।

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

के साथ तय किया

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

एक IQueryable के लिए, जब संभव हो, IQueryable में रहें, कोशिश करें कि IEnumerable की तरह उपयोग न किया जाए।

अद्यतन करें । इसे एक अभिव्यक्ति में और सरल बनाया जा सकता है, धन्यवाद गर्ट अर्नोल्ड

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.