1000 फ्रेमवर्क ऑब्जेक्ट्स बनाते समय मुझे SaveChanges () को कब कॉल करना चाहिए? (जैसे आयात के दौरान)


80

मैं एक आयात चला रहा हूं जिसमें प्रत्येक रन पर 1000 रिकॉर्ड होंगे। बस मेरी मान्यताओं पर कुछ पुष्टि की तलाश में:

इनमें से कौन सबसे अधिक समझदार है:

  1. SaveChanges()हर AddToClassName()कॉल को चलाएं ।
  2. SaveChanges()हर n नंबर पर AddToClassName()कॉल चलाएं ।
  3. भागो SaveChanges()के बाद सभी की AddToClassName()कॉल।

पहला विकल्प शायद धीमा सही है? चूँकि इसके लिए EF ऑब्जेक्ट्स को मेमोरी में एनालिसिस करना होगा, SQL जेनरेट करना होगा आदि।

मुझे लगता है कि दूसरा विकल्प दोनों दुनिया के लिए सबसे अच्छा है, क्योंकि हम उस SaveChanges()कॉल के चारों ओर एक कोशिश पकड़ को लपेट सकते हैं, और केवल एक बार में एन रिकॉर्ड खो देते हैं , अगर उनमें से एक विफल रहता है। शायद एक सूची में प्रत्येक बैच को स्टोर करें <>। यदि SaveChanges()कॉल सफल होता है, तो सूची से छुटकारा पाएं। यदि यह विफल रहता है, तो आइटम लॉग इन करें।

अंतिम विकल्प संभवतः बहुत धीमी गति से समाप्त हो जाएगा, क्योंकि हर एक ईएफ ऑब्जेक्ट को मेमोरी में तब तक रहना होगा जब तक SaveChanges()कि उसे कॉल नहीं किया जाता है। और अगर बचत विफल रही तो कुछ भी प्रतिबद्ध नहीं होगा, है ना?

जवाबों:


62

मैं यह सुनिश्चित करने के लिए पहले परीक्षण करूंगा। प्रदर्शन को उतना बुरा नहीं होना चाहिए।

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

दूसरा विकल्प मुझे पसंद नहीं है। यह मेरे लिए भ्रामक होगा (अंतिम उपयोगकर्ता के नजरिए से) अगर मैंने सिस्टम को आयात किया और यह 1000 में से 10 पंक्तियों को अस्वीकार कर देगा, क्योंकि सिर्फ 1 खराब है। आप 10 आयात करने का प्रयास कर सकते हैं और यदि यह विफल रहता है, तो एक-एक करके प्रयास करें और फिर लॉग करें।

अगर इसमें लंबा समय लगता है तो टेस्ट करें। 'प्रॉपर तरीके से' न लिखें। आप इसे अभी तक नहीं जानते हैं। केवल जब यह वास्तव में एक समस्या है, अन्य समाधान (marc_s) के बारे में सोचें।

संपादित करें

मैंने कुछ परीक्षण किए हैं (मिलिसेकंड में समय):

10000 पंक्तियाँ:

SaveChanges () 1 पंक्ति के बाद: 18510,534
SaveChanges () 100 पंक्तियों के बाद: 4350,3075
SaveChanges () 10000 पंक्तियों के बाद: 5233,0635

50000 पंक्तियाँ:

SaveChanges () 1 पंक्ति के बाद: 78496,929
SaveChanges () 500 पंक्तियों के बाद: 22302,2835
SaveChanges () 50000 पंक्तियों के बाद: 24022,8765

तो यह वास्तव में सभी के बाद की तुलना में n पंक्तियों के बाद करने के लिए तेज़ है।

मेरी सिफारिश है:

  • SaveRhanges () n row के बाद।
  • यदि कोई कमिट विफल रहता है, तो दोषपूर्ण पंक्ति को खोजने के लिए इसे एक-एक करके देखें।

परीक्षण कक्षाएं:

टेबल:

CREATE TABLE [dbo].[TestTable](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [SomeInt] [int] NOT NULL,
    [SomeVarchar] [varchar](100) NOT NULL,
    [SomeOtherVarchar] [varchar](50) NOT NULL,
    [SomeOtherInt] [int] NULL,
 CONSTRAINT [PkTestTable] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

वर्ग:

public class TestController : Controller
{
    //
    // GET: /Test/
    private readonly Random _rng = new Random();
    private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private string RandomString(int size)
    {
        var randomSize = _rng.Next(size);

        char[] buffer = new char[randomSize];

        for (int i = 0; i < randomSize; i++)
        {
            buffer[i] = _chars[_rng.Next(_chars.Length)];
        }
        return new string(buffer);
    }


    public ActionResult EFPerformance()
    {
        string result = "";

        TruncateTable();
        result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(10000, 1).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 100 rows:" + EFPerformanceTest(10000, 100).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 10000 rows:" + EFPerformanceTest(10000, 10000).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(50000, 1).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 500 rows:" + EFPerformanceTest(50000, 500).TotalMilliseconds + "<br/>";
        TruncateTable();
        result = result + "SaveChanges() after 50000 rows:" + EFPerformanceTest(50000, 50000).TotalMilliseconds + "<br/>";
        TruncateTable();

        return Content(result);
    }

    private void TruncateTable()
    {
        using (var context = new CamelTrapEntities())
        {
            var connection = ((EntityConnection)context.Connection).StoreConnection;
            connection.Open();
            var command = connection.CreateCommand();
            command.CommandText = @"TRUNCATE TABLE TestTable";
            command.ExecuteNonQuery();
        }
    }

    private TimeSpan EFPerformanceTest(int noOfRows, int commitAfterRows)
    {
        var startDate = DateTime.Now;

        using (var context = new CamelTrapEntities())
        {
            for (int i = 1; i <= noOfRows; ++i)
            {
                var testItem = new TestTable();
                testItem.SomeVarchar = RandomString(100);
                testItem.SomeOtherVarchar = RandomString(50);
                testItem.SomeInt = _rng.Next(10000);
                testItem.SomeOtherInt = _rng.Next(200000);
                context.AddToTestTable(testItem);

                if (i % commitAfterRows == 0) context.SaveChanges();
            }
        }

        var endDate = DateTime.Now;

        return endDate.Subtract(startDate);
    }
}

कारण मैंने लिखा "शायद" यह है कि मैंने एक शिक्षित अनुमान लगाया है। यह स्पष्ट करने के लिए कि "मुझे यकीन नहीं है", मैंने इसे एक प्रश्न में बनाया। इसके अलावा, मुझे लगता है कि उन संभावित समस्याओं के बारे में सोचने के लिए यह पूरी तरह से समझ में आता है जो मैं उनमें चलाता हूं। यही कारण है कि मैंने यह सवाल पूछा। मैं उम्मीद कर रहा था कि किसी को पता होगा कि कौन सा तरीका सबसे कुशल होगा, और मैं बल्ले से ही सही, उसके साथ जा सकता हूं।
जॉन Bubriski

बेहतरीन दोस्त। ठीक वही जो मेरे द्वारा खोजा जा रहा था। यह परीक्षण करने के लिए समय निकालने के लिए धन्यवाद! मैं अनुमान लगा रहा हूं कि मैं प्रत्येक बैच को मेमोरी में स्टोर कर सकता हूं, कमिट की कोशिश कर सकता हूं, और फिर अगर यह विफल रहता है तो हर एक के माध्यम से व्यक्तिगत रूप से कहो। फिर एक बार जब वह बैच किया जाता है, तो उन 100 वस्तुओं के संदर्भ जारी करें ताकि उन्हें स्मृति से बाहर निकाला जा सके। एक बार फिर धन्यवाद!
जॉन बुब्रिस्की

3
मेमोरी को मुक्त नहीं किया जाएगा, क्योंकि सभी ऑब्जेक्ट्स ऑब्जेक्ट कॉन्टेक्स्ट द्वारा आयोजित किए जाएंगे, लेकिन संदर्भ में 50000 या 100000 होने से इन दिनों ज्यादा जगह नहीं होती है।
११:३०

6
मैंने वास्तव में पाया है कि प्रदर्शन कॉलचेंज () में प्रत्येक कॉल के बीच में गिरावट है। इसका समाधान वास्तव में प्रत्येक SaveChanges () कॉल के बाद संदर्भ को निपटाना है, और जोड़े जाने वाले डेटा के अगले बैच के लिए एक नया पुन: त्वरित करना है।
शॉन डे वेट

1
@LukLed काफी नहीं ... आप अपने लूप के अंदर SaveChanges को कॉल कर रहे हैं ... इसलिए कोड लूप के लिए अधिक आइटम जोड़ने पर ले जा सकता है ctx के एक ही उदाहरण पर लूप के अंदर सेव होने के लिए और बाद में उसी उदाहरण पर SaveChanges को फिर से कॉल करें। ।
शॉन डे वेट

18

मैंने अपने कोड में एक बहुत ही समान समस्या को अनुकूलित किया है और मेरे लिए काम करने वाले अनुकूलन को इंगित करना चाहता हूं।

मैंने पाया कि SaveChanges को संसाधित करने में बहुत समय, चाहे एक बार में 100 या 1000 रिकॉर्ड संसाधित करना हो, CPU बाध्य है। इसलिए, एक निर्माता / उपभोक्ता पैटर्न (ब्लॉकिंगकॉलेक्शन के साथ कार्यान्वित) के संदर्भों को संसाधित करके, मैं सीपीयू कोर का अधिक बेहतर उपयोग करने में सक्षम था और कुल 4000 परिवर्तनों / सेकंड से प्राप्त हुआ (जैसा कि Savehanhanges के वापसी मूल्य द्वारा रिपोर्ट किया गया) 14,000 से अधिक परिवर्तन / सेकंड। CPU उपयोग लगभग 13% (मेरे पास 8 कोर) से लगभग 60% तक चला गया। यहां तक ​​कि कई उपभोक्ता थ्रेड्स का उपयोग करते हुए, मैंने बमुश्किल (बहुत तेज) डिस्क IO प्रणाली पर कर लगाया और SQL सर्वर का CPU उपयोग 15% से अधिक नहीं था।

बचत को कई थ्रेड्स में लोड करके, आपके पास कमिटमेंट करने से पहले रिकॉर्ड्स की संख्या और स्ट्राइक करने वाले थ्रेड्स की संख्या को ट्यून करने की क्षमता है।

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

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


नमस्ते, @ eric-j आप इस लाइन को थोड़ा "निर्माता / उपभोक्ता पैटर्न (ब्लॉकिंगकॉलक्शन के साथ कार्यान्वित) के साथ संदर्भों को संसाधित करके" विस्तृत कर सकते हैं ताकि मैं अपने कोड के साथ कोशिश कर सकूं?
फोयजुल करीम

14

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


15
मुझे इससे नफरत है जब लोग मेरे सवाल का जवाब नहीं देते हैं :) ठीक है, चलो कहते हैं कि मुझे ईएफ का उपयोग करने के लिए "आवश्यकता" है। फिर क्या?
जॉन Bubriski

3
ठीक है, अगर तुम सच में करना चाहिए एफई उपयोग करते हैं, तो मैं कहना 500 या 1000 के रिकॉर्ड का एक बैच के बाद प्रतिबद्ध करने के लिए कोशिश करेंगे। अन्यथा, आप बहुत अधिक संसाधनों का उपयोग करके समाप्त हो जाएंगे, और 100000 वें विफल होने पर आपके द्वारा अपडेट की गई सभी 99999 पंक्तियों को विफल कर देगा।
marc_s

इसी मुद्दे के साथ, मैंने SqlBulkCopy का उपयोग करके समाप्त किया जो कि उस मामले में अधिक प्रभावी है कि ईएफ। हालाँकि मुझे डेटाबेस तक पहुँचने के लिए कई तरीकों का उपयोग करना पसंद नहीं है।
जुलिएन एन

2
मैं इस समाधान को भी देख रहा हूं क्योंकि मुझे भी यही समस्या है ... बल्क कॉपी एक उत्कृष्ट समाधान होगा, लेकिन मेरी होस्टिंग सेवा इसका उपयोग नहीं करती है (और मुझे लगता है कि अन्य लोग भी इसका उपयोग करेंगे), इसलिए यह व्यवहार्य नहीं है कुछ लोगों के लिए विकल्प।
डेनिस वार्ड

3
@marc_s: SqlBulkCopy का उपयोग करते समय आप व्यावसायिक वस्तुओं में निहित व्यावसायिक नियमों को लागू करने की आवश्यकता को कैसे संभालते हैं? मैं यह नहीं देखता कि नियमों को लागू किए बिना ईएफ का उपयोग कैसे किया जाए।
एरिक जे।

2

एक संग्रहीत प्रक्रिया का उपयोग करें।

  1. Sql Server में User-Defined Data Type बनाएँ।
  2. अपने कोड में इस प्रकार की एक सरणी बनाएं और आबाद करें (बहुत तेज़)।
  3. सरणी को एक कॉल (बहुत तेज़) के साथ अपनी संग्रहीत कार्यविधि में पास करें।

मेरा मानना ​​है कि यह ऐसा करने का सबसे आसान और सबसे तेज़ तरीका होगा।


7
आमतौर पर SO पर, "यह सबसे तेज़ है" के दावों को परीक्षण कोड और परिणामों के साथ प्रमाणित करने की आवश्यकता है।
माइकल ब्लैकबर्न

2

क्षमा करें, मुझे पता है कि यह धागा पुराना है, लेकिन मुझे लगता है कि इससे अन्य लोगों को इस समस्या में मदद मिल सकती है।

मुझे भी यही समस्या थी, लेकिन आपके द्वारा किए जाने से पहले परिवर्तनों को मान्य करने की संभावना है। मेरा कोड इस तरह दिखता है और यह ठीक काम कर रहा है। chUser.LastUpdatedअगर मैं एक नई प्रविष्टि है या केवल एक परिवर्तन है, तो मैं इसकी जांच करता हूं। क्योंकि ऐसी प्रविष्टि को पुनः लोड करना संभव नहीं है जो अभी तक डेटाबेस में नहीं है।

// Validate Changes
var invalidChanges = _userDatabase.GetValidationErrors();
foreach (var ch in invalidChanges)
{
    // Delete invalid User or Change
    var chUser  =  (db_User) ch.Entry.Entity;
    if (chUser.LastUpdated == null)
    {
        // Invalid, new User
        _userDatabase.db_User.Remove(chUser);
        Console.WriteLine("!Failed to create User: " + chUser.ContactUniqKey);
    }
    else
    {
        // Invalid Change of an Entry
        _userDatabase.Entry(chUser).Reload();
        Console.WriteLine("!Failed to update User: " + chUser.ContactUniqKey);
    }                    
}

_userDatabase.SaveChanges();

हाँ, यह उसी समस्या के बारे में है, है ना? इसके साथ, आप सभी 1000 रिकॉर्ड जोड़ सकते हैं और दौड़ने से पहले आप saveChanges()उन लोगों को हटा सकते हैं जिनके कारण त्रुटि होगी।
Jan Leuenberger

1
लेकिन सवाल का जोर इस बात पर है कि एक SaveChangesकॉल में कितने इन्सर्ट / अपडेट्स को कुशलता से करना है । आप उस समस्या का समाधान नहीं करते हैं। नोट करें कि सत्यापन त्रुटियों की तुलना में SaveChanges के विफल होने के अधिक संभावित कारण हैं। वैसे, आप केवल Unchangedपुनः लोड / हटाने के बजाय संस्थाओं को भी चिह्नित कर सकते हैं।
गर्ट अर्नोल्ड

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

इस बारे में मेरा एक प्रश्न है। जब आप कॉल करते हैं GetValidationErrors()तो यह डेटाबेस के लिए "नकली" कॉल करता है और त्रुटियों को पुनर्प्राप्त करता है या क्या? जवाब देने के लिए धन्यवाद :)
जीनकरलो फानाल्वो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.