कंटेस्टेंट्स () ऑपरेटर एंटिटी फ्रेमवर्क के प्रदर्शन को इतनी नाटकीयता से क्यों नीचा दिखाता है?


79

अद्यतन 3: इस घोषणा के अनुसार , यह EF6 अल्फा 2 में EF टीम द्वारा संबोधित किया गया है।

अद्यतन 2: मैंने इस समस्या को ठीक करने के लिए एक सुझाव दिया है। इसे वोट करने के लिए, यहां जाएं

एक बहुत ही सरल तालिका के साथ एक SQL डेटाबेस पर विचार करें।

CREATE TABLE Main (Id INT PRIMARY KEY)

मैं 10,000 रिकॉर्ड के साथ तालिका को आबाद करता हूं।

WITH Numbers AS
(
  SELECT 1 AS Id
  UNION ALL
  SELECT Id + 1 AS Id FROM Numbers WHERE Id <= 10000
)
INSERT Main (Id)
SELECT Id FROM Numbers
OPTION (MAXRECURSION 0)

मैं तालिका के लिए एक EF मॉडल बनाता हूं और LINQPad में निम्नलिखित क्वेरी चलाता हूं (मैं "C # कथन" मोड का उपयोग कर रहा हूं ताकि LINQPad स्वचालित रूप से एक डंप नहीं बना सके)।

var rows = 
  Main
  .ToArray();

निष्पादन का समय ~ 0.07 सेकंड है। अब मैं Contains ऑपरेटर जोड़ता हूं और क्वेरी को फिर से चलाता हूं।

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  Main
  .Where (a => ids.Contains(a.Id))
  .ToArray();

इस मामले का निष्पादन समय 20.14 सेकंड (288 गुना धीमा) है!

पहले मुझे संदेह था कि क्वेरी के लिए उत्सर्जित T-SQL निष्पादन में अधिक समय ले रहा है, इसलिए मैंने LINQPad के SQL फलक से SQL सर्वर प्रबंधन स्टूडियो में इसे काटने और चिपकाने की कोशिश की।

SET NOCOUNT ON
SET STATISTICS TIME ON
SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2,3,4,5,6,7,8,...

और परिणाम था

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 88 ms.

अगला मुझे शक था कि लिनक्यूपैड समस्या पैदा कर रहा है, लेकिन प्रदर्शन वही है कि मैं इसे लिनक्यूपैड में चलाऊं या कंसोल एप्लिकेशन में।

तो, ऐसा प्रतीत होता है कि समस्या कहीं न कहीं एंटिटी फ्रेमवर्क के भीतर है।

क्या मुझसे यहां कुछ गलत हो रहा है? यह मेरे कोड का एक समय-महत्वपूर्ण हिस्सा है, इसलिए प्रदर्शन को गति देने के लिए मैं कुछ कर सकता हूं?

मैं Entity Framework 4.1 और Sql Server 2008 R2 का उपयोग कर रहा हूं।

अद्यतन 1:

नीचे चर्चा में कुछ सवाल थे कि क्या ईएफ प्रारंभिक क्वेरी का निर्माण करते समय देरी हो रही थी या जब यह डेटा वापस प्राप्त कर रहा था तो यह पार्स कर रहा था। इसका परीक्षण करने के लिए मैंने निम्नलिखित कोड चलाया,

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  (ObjectQuery<MainRow>)
  Main
  .Where (a => ids.Contains(a.Id));
var sql = rows.ToTraceString();

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

बचाव के लिए CompiledQuery तो? इतनी जल्दी नहीं ... CompiledQuery को मौलिक प्रकार (इंट, स्ट्रिंग, फ्लोट, और इसी तरह) होने के लिए पैरामीटर में क्वेरी की आवश्यकता होती है। यह सरणियों या IEnumerable को स्वीकार नहीं करेगा, इसलिए मैं इसे Ids की सूची के लिए उपयोग नहीं कर सकता।


1
क्या आपने var qry = Main.Where (a => ids.Contains(a.Id)); var rows = qry.ToArray();यह देखने की कोशिश की है कि क्वेरी के किस भाग में समय लग रहा है?
एंड्रयू कूपर

यह आपकी क्वेरी को नीचा दिखाने वाला EF नहीं है, यह वास्तविक क्वेरी है जिसे आप चलाने की कोशिश कर रहे हैं; क्या आप बता सकते हैं कि आप क्या करने की कोशिश कर रहे हैं? शायद आपकी आवश्यकताओं के लिए एक बेहतर दृष्टिकोण है
क्रिश इवानोव

@AndrewCooper मैंने इसे अभी आज़माया है, और आस्थगित निष्पादन के कारण पहला कथन (ToArray के बिना) लगभग तुरंत निष्पादित करता है। जिसमें फ़िल्टर शामिल है, क्वेरी वास्तव में तब तक नहीं चलती है जब तक कि आप ToArray () को निष्पादित नहीं करते हैं।
माइक

5
बस और इस पर अपडेट करें: EF6 अल्फा 2 में एक सुधार शामिल है जो Enumerable.Contains के अनुवाद को तेज करता है। यहां घोषणा देखें: blogs.msdn.com/b/adonet/archive/2012/12/10/… । मेरे अपने परीक्षण बताते हैं कि 100,000 अंतर तत्वों वाली सूची के लिए सूची का अनुवाद करना। (एक्स) अब एक सेकंड के तहत अच्छी तरह से लेता है, और समय सूची में तत्वों की संख्या के साथ लगभग रैखिक रूप से बढ़ता है। आपकी प्रतिक्रिया के लिए धन्यवाद और ईएफ को बेहतर बनाने में हमारी मदद करें!
दिविगा

1
इस से सावधान रहें ... किसी भी IEnumerable पैरामीटर के साथ प्रश्नों को कैश नहीं किया जा सकता है, जो आपकी क्वेरी योजनाओं के जटिल होने पर काफी गंभीर दुष्प्रभाव पैदा कर सकता है। यदि आपको कई बार ऑपरेशन चलाना है (जैसे डेटा का हिस्सा पाने के लिए इसमें कंटेस्टेंट्स का उपयोग करना) तो आपके पास कुछ बहुत बुरा प्रश्न फिर से हो सकता है! अपने लिए स्रोत की जाँच करें और आप देख सकते हैं कि parent._recompileRequired = () => true;IEnumerable <T> पैरामीटर वाले सभी प्रश्नों के लिए होता है। बू!
jocull

जवाबों:


66

अद्यतन: EF6 में InExpression के अलावा, Enumerable.Contains प्रसंस्करण के प्रदर्शन में नाटकीय रूप से वृद्धि हुई है। इस उत्तर में वर्णित दृष्टिकोण अब आवश्यक नहीं है।

आप सही हैं कि अधिकांश समय क्वेरी के अनुवाद को संसाधित करने में बिताया जाता है। ईएफ के प्रदाता मॉडल में वर्तमान में एक अभिव्यक्ति शामिल नहीं है जो एक IN क्लॉज का प्रतिनिधित्व करता है, इसलिए ADO.NET प्रदाता मूल रूप से समर्थन नहीं कर सकते हैं। इसके बजाय, Enumerable.Contains का कार्यान्वयन इसे OR अभिव्यक्तियों के पेड़ में अनुवाद करता है, अर्थात C # में कुछ इस तरह दिखता है:

new []{1, 2, 3, 4}.Contains(i)

... हम एक DbExpression ट्री बनाएंगे जिसे इस तरह दर्शाया जा सकता है:

((1 = @i) OR (2 = @i)) OR ((3 = @i) OR (4 = @i))

(अभिव्यक्ति के पेड़ों को संतुलित करना होगा क्योंकि अगर हम एक लंबे रीढ़ पर सभी ओआरएस थे, तो अधिक संभावना होगी कि अभिव्यक्ति आगंतुक एक स्टैक अतिप्रवाह को मार देगा (हाँ, हमने वास्तव में हमारे परीक्षण में मारा था)

हम बाद में ADO.NET प्रदाता को इस तरह एक पेड़ भेजते हैं, जो इस पैटर्न को पहचानने की क्षमता और SQL पीढ़ी के दौरान IN खंड में इसे कम कर सकता है।

जब हमने EF4 में Enumerable.Contains के लिए समर्थन जोड़ा, तो हमने सोचा कि प्रदाता मॉडल में अभिव्यक्तियों के लिए समर्थन शुरू किए बिना इसे करना वांछनीय था, और ईमानदारी से, 10,000 हम उन तत्वों की संख्या से अधिक हैं जिन्हें हम ग्राहकों की अपेक्षा से गुजरेंगे। Enumerable.Contains। उस ने कहा, मैं समझता हूं कि यह एक झुंझलाहट है और अभिव्यक्ति के वृक्षों का हेरफेर चीजों को आपके विशेष परिदृश्य में बहुत महंगा बनाता है।

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

पहले से सुझाए गए वर्कअराउंड में मैं निम्नलिखित बातें जोड़ूंगा:

एक विधि बनाने पर विचार करें जो डेटाबेस राउंडट्रिप्स की संख्या को संतुलित करता है जिसमें आप शामिल हैं। उदाहरण के लिए, अपने स्वयं के परीक्षण में मैंने देखा कि कंप्यूटिंग और SQL सर्वर के स्थानीय उदाहरण के खिलाफ 100 तत्वों के साथ क्वेरी एक सेकंड का 1/60 लेता है। यदि आप अपनी क्वेरी को इस तरह से लिख सकते हैं कि आईडी के 100 अलग-अलग सेट के साथ 100 प्रश्नों को निष्पादित करने से आपको 10,000 तत्वों के साथ क्वेरी के बराबर परिणाम मिलेगा, तो आप 18 सेकंड के बजाय 1.67 सेकंड में परिणाम प्राप्त कर सकते हैं।

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

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

उपयोग:

var list = context.GetMainItems(ids).ToList();

संदर्भ या भंडार के लिए विधि:

public partial class ContainsTestEntities
{
    public IEnumerable<Main> GetMainItems(IEnumerable<int> ids, int chunkSize = 100)
    {
        foreach (var chunk in ids.Chunk(chunkSize))
        {
            var q = this.MainItems.Where(a => chunk.Contains(a.Id));
            foreach (var item in q)
            {
                yield return item;
            }
        }
    }
}

विस्तार योग्य अनुक्रमों को काटने के लिए विस्तार विधियाँ:

public static class EnumerableSlicing
{

    private class Status
    {
        public bool EndOfSequence;
    }

    private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count, 
        Status status)
    {
        while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true)))
        {
            yield return enumerator.Current;
        }
    }

    public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize)
    {
        if (chunkSize < 1)
        {
            throw new ArgumentException("Chunks should not be smaller than 1 element");
        }
        var status = new Status { EndOfSequence = false };
        using (var enumerator = items.GetEnumerator())
        {
            while (!status.EndOfSequence)
            {
                yield return TakeOnEnumerator(enumerator, chunkSize, status);
            }
        }
    }
}

उम्मीद है की यह मदद करेगा!


!(status.EndOfSequence = true)टेकऑनइंटरमीटर <टी> विधि में व्याख्या करने के लिए : तो इस अभिव्यक्ति असाइनमेंट का साइड इफेक्ट हमेशा रहेगा! यह सच है कि समग्र अभिव्यक्ति को प्रभावित नहीं करता है। यह अनिवार्य stats.EndOfSequenceरूप से trueकेवल तब ही चिह्नित होता है जब शेष वस्तुएं प्राप्त की जानी हैं, लेकिन आपने गणना के अंत में मारा है।
अरविमान

शायद Enumerable.Containsईएफ 6 के पिछले संस्करणों की तुलना में ईएफ 6 में प्रसंस्करण के प्रदर्शन में नाटकीय रूप से सुधार हुआ है। लेकिन, दुर्भाग्य से, यह अभी भी हमारे उपयोग के मामलों में संतोषजनक / उत्पादन-तैयार से दूर है।
निक

24

यदि आपको एक प्रदर्शन समस्या है, जो आपके लिए अवरुद्ध है, तो इसे हल करने में उम्र बिताने की कोशिश न करें क्योंकि आप शायद सबसे सफल नहीं होंगे और आपको इसे सीधे एमएस के साथ संवाद करना होगा (यदि आपके पास प्रीमियम समर्थन है) और इसे लेता है उम्र।

प्रदर्शन समस्या के मामले में वर्कअराउंड और वर्कअराउंड का उपयोग करें और EF का अर्थ है प्रत्यक्ष SQL। इसके बारे में कुछ भी बुरा नहीं है। वैश्विक विचार है कि EF = SQL का उपयोग नहीं करना एक झूठ है। आपके पास SQL ​​Server 2008 R2 है:

  • अपनी आईडी पास करने के लिए टेबल वैल्यू पैरामीटर को स्वीकार करते हुए संग्रहित प्रक्रिया बनाएं
  • Includeइष्टतम तरीके से तर्क का अनुकरण करने के लिए अपनी संग्रहीत प्रक्रिया को कई परिणाम सेट लौटाएं
  • यदि आपको कुछ जटिल क्वेरी बिल्डिंग की आवश्यकता है तो संग्रहीत कार्यविधि के अंदर गतिशील SQL का उपयोग करें
  • SqlDataReaderपरिणाम प्राप्त करने और अपनी संस्थाओं का निर्माण करने के लिए उपयोग करें
  • उन्हें संदर्भ में संलग्न करें और उनके साथ काम करें जैसे कि वे ईएफ से लोड किए गए थे

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


@Laddislav Mrnka हमें सूची के कारण समान प्रदर्शन का सामना करना पड़ा। कॉन्टैक्ट्स ()। हम आईडी पास करके प्रक्रियाएँ बनाने की कोशिश करने जा रहे हैं। अगर हम ईएफ के माध्यम से इस प्रक्रिया को चलाते हैं, तो क्या हमें किसी भी प्रदर्शन को प्रभावित करना चाहिए?
कुरुबरन

9

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

एक सिंहावलोकन:

  • उदाहरण के लिए - एसक्यूएल सर्वर में एक तालिका बनाने के HelperForContainsOfIntTypeसाथ HelperIDकी Guidडेटा प्रकार और ReferenceIDका intडेटा प्रकार स्तंभ। आवश्यकतानुसार अलग-अलग डेटा-प्रकार के संदर्भ के साथ अलग-अलग तालिकाएँ बनाएँ।

  • HelperForContainsOfIntTypeEF मॉडल में ऐसी और अन्य तालिकाओं के लिए एक इकाई / EntitySet बनाएं । आवश्यकतानुसार विभिन्न डेटा-प्रकारों के लिए अलग-अलग एंटिटी / एंटिटीसेट बनाएं।

  • .NET कोड में एक सहायक विधि बनाएँ जो इनपुट का IEnumerable<int>रिटर्न लेता है और रिटर्न करता है Guid। इस विधि के लिए एक नया उत्पन्न करता है Guidऔर से मूल्यों को सम्मिलित करता है IEnumerable<int>में HelperForContainsOfIntTypeउत्पन्न के साथ Guid। इसके बाद, विधि Guidकॉल करने वाले के लिए इस नए को लौटा देती है। HelperForContainsOfIntTypeतालिका में तेजी से सम्मिलित करने के लिए, एक संग्रहीत-प्रक्रिया बनाएं जो मानों की सूची का इनपुट लेता है और सम्मिलन करता है। SQL सर्वर 2008 (ADO.NET) में तालिका-मान्य पैरामीटर देखें । विभिन्न डेटा-प्रकारों के लिए अलग-अलग सहायक बनाएं या विभिन्न डेटा-प्रकारों को संभालने के लिए एक सामान्य सहायक विधि बनाएं।

  • एक EF संकलित क्वेरी बनाएँ जो नीचे की तरह कुछ के समान है:

    static Func<MyEntities, Guid, IEnumerable<Customer>> _selectCustomers =
        CompiledQuery.Compile(
            (MyEntities db, Guid containsHelperID) =>
                from cust in db.Customers
                join x in db.HelperForContainsOfIntType on cust.CustomerID equals x.ReferenceID where x.HelperID == containsHelperID
                select cust 
        );
    
  • क्लॉज़र में उपयोग किए जाने वाले मानों के साथ सहायक विधि को कॉल करें Containsऔर Guidक्वेरी में उपयोग करने के लिए प्राप्त करें । उदाहरण के लिए:

    var containsHelperID = dbHelper.InsertIntoHelperForContainsOfIntType(new int[] { 1, 2, 3 });
    var result = _selectCustomers(_dbContext, containsHelperID).ToList();
    

इसके लिए धन्यवाद! मैंने आपकी समस्या को हल करने के लिए आपके समाधान की एक विविधता का उपयोग किया।
माइक

5

मेरे मूल उत्तर का संपादन - आपकी संस्थाओं की जटिलता के आधार पर, एक संभावित समाधान है। यदि आपको पता है कि ईएलएफ आपकी संस्थाओं को आबाद करने के लिए उत्पन्न होता है, तो आप इसे सीधे DbContext.Database.SqlQuery का उपयोग करके निष्पादित कर सकते हैं । EF 4 में, मुझे लगता है कि आप ObjectContext.ExecuteStoreQuery का उपयोग कर सकते हैं , लेकिन मैंने इसकी कोशिश नहीं की।

उदाहरण के लिए, नीचे दिए गए sql स्टेटमेंट को जेनरेट करने के लिए मेरे मूल उत्तर से कोड का उपयोग करके StringBuilder, मैं निम्नलिखित कार्य करने में सक्षम था

var rows = db.Database.SqlQuery<Main>(sql).ToArray();

और कुल समय लगभग 26 सेकंड से 0.5 सेकंड तक चला गया।

मैं यह कहने वाला पहला व्यक्ति हूँ कि यह बदसूरत है, और उम्मीद है कि एक बेहतर समाधान खुद को प्रस्तुत करता है।

अपडेट करें

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

इसका परीक्षण करने के लिए, मैंने Targetउसी स्कीमा के साथ एक तालिका बनाई, जैसा कि Main। मैंने तब 1000 के बैच में टेबल को पॉप्युलेट StringBuilderकरने के लिए INSERTकमांड बनाने के लिए एक का उपयोग किया Targetक्योंकि यह सबसे SQL सर्वर एक में स्वीकार करेगा INSERT। प्रत्यक्ष रूप से sql स्टेटमेंट्स को निष्पादित करना EF (लगभग 0.3 सेकंड बनाम 2.5 सेकंड) के माध्यम से जाने की तुलना में बहुत तेज़ था, और मेरा मानना ​​है कि ठीक होगा क्योंकि तालिका स्कीमा को बदलना नहीं चाहिए।

अंत में, joinएक बहुत ही सरल क्वेरी के परिणामस्वरूप चयन करते हुए और 0.5 सेकंड से भी कम समय में निष्पादित किया गया।

ExecuteStoreCommand("DELETE Target");

var ids = Main.Select(a => a.Id).ToArray();
var sb = new StringBuilder();

for (int i = 0; i < 10; i++)
{
    sb.Append("INSERT INTO Target(Id) VALUES (");
    for (int j = 1; j <= 1000; j++)
    {
        if (j > 1)
        {
            sb.Append(",(");
        }
        sb.Append(i * 1000 + j);
        sb.Append(")");
    }
    ExecuteStoreCommand(sb.ToString());
    sb.Clear();
}

var rows = (from m in Main
            join t in Target on m.Id equals t.Id
            select m).ToArray();

rows.Length.Dump();

और शामिल होने के लिए EF द्वारा उत्पन्न वर्ग:

SELECT 
[Extent1].[Id] AS [Id]
FROM  [dbo].[Main] AS [Extent1]
INNER JOIN [dbo].[Target] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]

(मूल उत्तर)

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

SQL Profiler दर्शाता है कि देरी पहली क्वेरी ( Main.Select) और दूसरी Main.Whereक्वेरी के निष्पादन के बीच है , इसलिए मुझे संदेह था कि समस्या उस आकार (48,980 बाइट्स) की क्वेरी बनाने और भेजने में थी।

हालाँकि, टी-एसक्यूएल में एक ही sql स्टेटमेंट का निर्माण गतिशील रूप से 1 सेकंड से कम समय लेता है, और idsआपके Main.Selectस्टेटमेंट से, उसी sql स्टेटमेंट SqlCommandको बनाते हुए और इसे 0.112 सेकंड का उपयोग करके निष्पादित करता है , और यह कंसोल के लिए कंटेंट लिखने के लिए समय सहित है। ।

इस बिंदु पर, मुझे संदेह है कि ईएफ 10,000 idsमें से प्रत्येक के लिए कुछ विश्लेषण / प्रसंस्करण कर रहा है क्योंकि यह क्वेरी बनाता है। काश मैं एक निश्चित उत्तर और समाधान प्रदान कर पाता :(

यहां मैंने SSMS और LINQPad में कोड की कोशिश की है (कृपया बहुत कठोरता से आलोचना न करें, मैं काम छोड़ने की कोशिश कर रहा हूं:

declare @sql nvarchar(max)

set @sql = 'SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Main] AS [Extent1]
WHERE [Extent1].[Id] IN ('

declare @count int = 0
while @count < 10000
begin
    if @count > 0 set @sql = @sql + ','
    set @count = @count + 1
    set @sql = @sql + cast(@count as nvarchar)
end
set @sql = @sql + ')'

exec(@sql)

var ids = Mains.Select(a => a.Id).ToArray();

var sb = new StringBuilder();
sb.Append("SELECT [Extent1].[Id] AS [Id] FROM [dbo].[Main] AS [Extent1] WHERE [Extent1].[Id] IN (");
for(int i = 0; i < ids.Length; i++)
{
    if (i > 0) 
        sb.Append(",");     
    sb.Append(ids[i].ToString());
}
sb.Append(")");

using (SqlConnection connection = new SqlConnection("server = localhost;database = Test;integrated security = true"))
using (SqlCommand command = connection.CreateCommand())
{
    command.CommandText = sb.ToString();
    connection.Open();
    using(SqlDataReader reader = command.ExecuteReader())
    {
        while(reader.Read())
        {
            Console.WriteLine(reader.GetInt32(0));
        }
    }
}

इस पर आपके काम के लिए धन्यवाद। यह जानते हुए कि आप इसे पुन: पेश करने में सक्षम थे, इससे मुझे बेहतर महसूस होता है - कम से कम मैं पागल नहीं हूं! दुर्भाग्य से आपका वर्कअराउंड वास्तव में मेरे मामले में मदद नहीं करता है, क्योंकि जैसा कि आप अनुमान लगा सकते हैं, मैंने जो उदाहरण यहां दिया है उसे समस्या को अलग करने के लिए यथासंभव सरल बनाया गया था। मेरी वास्तविक क्वेरी में एक बहुत ही जटिल स्कीमा शामिल है, कई अन्य तालिकाओं में .Include () का, और कुछ अन्य LINQ ऑपरेटरों का भी।
माइक

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

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

@ माय, क्या आप आईडी से जुड़ने का प्रयास कर सकते हैं (मेरे उत्तर में अपडेट देखें)?
जेफ ओगाटा

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

5

मैं एंटिटी फ्रेमवर्क से परिचित नहीं हूँ, लेकिन क्या आप निम्नलिखित करें तो बेहतर है?

इसके अलावा:

var ids = Main.Select(a => a.Id).ToArray();
var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();

इसके बारे में कैसे (आईडी मान रहा है):

var ids = new HashSet<int>(Main.Select(a => a.Id));
var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();

मैं क्यों और कैसे नहीं, लेकिन यह एक आकर्षण की तरह काम किया है :) बहुत बहुत धन्यवाद :)
वाहिद बितर

1
प्रदर्शन बेहतर क्यों है, इसकी व्याख्या int [] है। पहली कॉल में कॉन्टेक्ट्स O (n) है - संभवतः एक फुल एरे स्कैन - जबकि हैशसेट <int>। कॉल कॉल O (1) है। हैशसेट प्रदर्शन के लिए stackoverflow.com/questions/9812020/… देखें ।
शिव

3
@ शिव मुझे विश्वास नहीं है कि यह सही है। ईएफ किसी भी संग्रह को ले जाएगा और इसे एसक्यूएल में अनुवाद करेगा। संग्रह का प्रकार एक गैर-मुद्दा होना चाहिए।
रोब

@ रब मुझे संदेह है - प्रदर्शन अंतर की व्याख्या करने के लिए एक नुकसान पर अगर ऐसा है तो। बाइनरी को यह देखने के लिए विश्लेषण करना पड़ सकता है कि उसने क्या किया है।
शिव

1
हैशसेट IEnumerable नहीं है। IEnumerables कॉलिंग। LINQ में कॉन्टैक्ट्स खराब प्रदर्शन करते हैं (कम से कम प्री-ईएफ
जेसन बेक


2

इसमें शामिल करने के लिए एक उपलब्ध विकल्प?

यह सिर्फ मुझे थोड़ा सा इसलिए मैंने एंटिटी फ्रेमवर्क फ़ीचर सुझाव लिंक में अपने दो पेंस जोड़े हैं।

SQL उत्पन्न करते समय समस्या निश्चित रूप से है। मेरे पास एक क्लाइंट है जिसके डेटा का क्वेरी जनरेशन 4 सेकंड था लेकिन निष्पादन 0.1 सेकंड था।

मैंने देखा कि डायनेमिक LINQ और ORs का उपयोग करते समय sql जेनरेशन को बस उतना ही समय लग रहा था लेकिन इसने कुछ ऐसा उत्पन्न किया जिसे कैश किया जा सकता था । इसलिए जब इसे फिर से निष्पादित किया गया तो यह 0.2 सेकंड तक नीचे था।

ध्यान दें कि SQL अभी भी जनरेट किया गया था।

बस कुछ और विचार करें कि क्या आप प्रारंभिक हिट को पेट कर सकते हैं, आपकी सरणी संख्या में बहुत बदलाव नहीं होता है, और क्वेरी को बहुत अधिक चलाएं। (LINQ पैड में परीक्षण किया गया)


इसके अलावा codeplex साइट पर उसके लिए वोट < entityframework.codeplex.com/workitem/245 >
डेव

2

समस्या एंटिटी फ्रेमवर्क की SQL पीढ़ी के साथ है। यदि पैरामीटर एक सूची है, तो यह क्वेरी को कैश नहीं कर सकता है।

अपनी क्वेरी को कैश करने के लिए EF प्राप्त करने के लिए आप अपनी सूची को एक स्ट्रिंग में बदल सकते हैं और एक स्ट्रिंग पर कर सकते हैं।

उदाहरण के लिए यह कोड बहुत तेज़ी से चलेगा क्योंकि EF क्वेरी को कैश कर सकता है:

var ids = Main.Select(a => a.Id).ToArray();
var idsString = "|" + String.Join("|", ids) + "|";
var rows = Main
.Where (a => idsString.Contains("|" + a.Id + "|"))
.ToArray();

जब यह क्वेरी उत्पन्न होती है, तो संभवत: इसे इसके बजाय एक जैसे का उपयोग करके उत्पन्न किया जाएगा, इसलिए यह आपके C # को गति देगा लेकिन यह संभवतः आपके SQL को धीमा कर सकता है। मेरे मामले में मैंने अपने एसक्यूएल निष्पादन में किसी भी प्रदर्शन में कमी नहीं देखी, और सी # काफी तेजी से भाग गया।


1
अच्छा विचार है, लेकिन यह प्रश्न में कॉलम पर किसी भी सूचकांक का उपयोग नहीं करेगा।
खर्चा

हां, यह सच है, यही कारण है कि मैंने उल्लेख किया है कि यह SQL निष्पादन को धीमा कर सकता है। मुझे लगता है कि यह सिर्फ एक संभावित विकल्प है अगर आप विधेय बिल्डर का उपयोग नहीं कर सकते हैं और आप एक छोटे से पर्याप्त डेटा सेट के साथ काम कर रहे हैं ताकि आप एक सूचकांक का उपयोग न कर सकें। मुझे यह भी लगता है कि मुझे यह उल्लेख करना चाहिए कि विधेय बिल्डर का पसंदीदा विकल्प है
user2704238

1
क्या एक अद्भुत समाधान है। हम अपने उत्पादन क्वेरी रनटाइम को ~ 12,600 मिलीसेकंड से बढ़ाकर केवल ~ 18 मिलीसेकंड करने में कामयाब रहे। यह बहुत बड़ा सुधार है। आपका बहुत बहुत धन्यवाद !!!
याकूब
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.