c# - इकाई फ्रेमवर्क में सम्मिलित करने का सबसे तेज़ तरीका




sql entity-framework (16)

मैं एंटिटी फ्रेमवर्क में डालने का सबसे तेज़ तरीका ढूंढ रहा हूं

बल्क सम्मिलन का समर्थन करने वाली कुछ तीसरी पार्टी लाइब्रेरी उपलब्ध है:

  • Z.EntityFramework.Extensions ( अनुशंसित )
  • EFUtilities
  • EntityFramework.BulkInsert

देखें: इकाई फ्रेमवर्क थोक सम्मिलित लाइब्रेरी

थोक डालने लाइब्रेरी चुनते समय सावधान रहें। केवल इकाई फ्रेमवर्क एक्सटेंशन सभी प्रकार के संगठनों और विरासत का समर्थन करते हैं और यह अभी भी समर्थित है।

अस्वीकरण : मैं इकाई फ्रेमवर्क एक्सटेंशन का मालिक हूं

यह पुस्तकालय आपको अपने परिदृश्यों के लिए आवश्यक सभी थोक संचालन करने की अनुमति देता है:

  • थोक SaveChanges
  • थोक सम्मिलित करें
  • थोक हटाएं
  • थोक अद्यतन
  • थोक मर्ज

उदाहरण

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

मैं एंटिटी फ्रेमवर्क में डालने का सबसे तेज़ तरीका ढूंढ रहा हूं।

मैं इस परिदृश्य के कारण यह पूछ रहा हूं कि आपके पास एक सक्रिय लेनदेनस्कोप है और सम्मिलन विशाल है (4000+)। यह संभावित रूप से 10 मिनट से अधिक (लेनदेन का डिफ़ॉल्ट समय समाप्ति) कर सकता है, और इससे अपूर्ण लेनदेन हो जाएगा।


आपके प्रश्न पर टिप्पणियों में आपकी टिप्पणी के लिए:

"... सेविंग चेंज ( प्रत्येक रिकॉर्ड के लिए ) ..."

यह सबसे बुरी चीज है जो आप कर सकते हैं! प्रत्येक रिकॉर्ड के लिए SaveChanges() को कॉल करना थोक प्रविष्टियों को बहुत धीमा कर देता है। मैं कुछ सरल परीक्षण करता हूं जो प्रदर्शन में काफी सुधार करेंगे:

  • सभी रिकॉर्ड के बाद एक बार SaveChanges() कॉल करें।
  • उदाहरण के लिए 100 रिकॉर्ड के बाद SaveChanges() को कॉल करें।
  • उदाहरण के लिए SaveChanges() को कॉल करें उदाहरण के लिए 100 रिकॉर्ड और संदर्भ का निपटान करें और एक नया बनाएं।
  • परिवर्तन पहचान अक्षम करें

थोक आवेषण के लिए मैं इस तरह के पैटर्न के साथ काम कर रहा हूं और प्रयोग कर रहा हूं:

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;            
        foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

private MyDbContext AddToContext(MyDbContext context,
    Entity entity, int count, int commitCount, bool recreateContext)
{
    context.Set<Entity>().Add(entity);

    if (count % commitCount == 0)
    {
        context.SaveChanges();
        if (recreateContext)
        {
            context.Dispose();
            context = new MyDbContext();
            context.Configuration.AutoDetectChangesEnabled = false;
        }
    }

    return context;
}

मेरे पास एक परीक्षण कार्यक्रम है जो डीबी में 560,000 इकाइयों (9 स्केलर गुण, कोई नेविगेशन गुण) सम्मिलित करता है। इस कोड के साथ यह 3 मिनट से भी कम समय में काम करता है।

प्रदर्शन के लिए "कई" रिकॉर्ड ("कई" लगभग 100 या 1000) के बाद SaveChanges() को कॉल करना महत्वपूर्ण है। यह SaveChanges के बाद संदर्भ को निपटाने और एक नया निर्माण करने के प्रदर्शन को भी बेहतर बनाता है। यह सभी entites से संदर्भ को साफ़ करता है, SaveChanges ऐसा नहीं करता है, संस्थाएं अभी भी राज्य Unchanged संदर्भ में संलग्न हैं। यह संदर्भ में संलग्न इकाइयों का बढ़ता आकार है जो कदम से सम्मिलन चरण को धीमा कर देता है। तो, कुछ समय बाद इसे साफ़ करना सहायक होता है।

यहां मेरी 560,000 इकाइयों के लिए कुछ माप दिए गए हैं:

  • commitकाउंट = 1, recreateContext = false: कई घंटे (यह आपकी वर्तमान प्रक्रिया है)
  • commitकाउंट = 100, recreateContext = false: 20 मिनट से अधिक
  • commitकाउंट = 1000, recreateContext = false: 242 सेकंड
  • commitकाउंट = 10000, recreateContext = false: 202 सेकंड
  • commitकाउंट = 100000, recreateContext = false: 199 सेकंड
  • commitकाउंट = 1000000, recreateContext = false: स्मृति अपवाद से बाहर
  • commitकाउंट = 1, recreateContext = true: 10 मिनट से अधिक
  • commitकाउंट = 10, recreateContext = true: 241 सेकंड
  • commitकाउंट = 100, recreateContext = true: 164 सेकंड
  • commitकाउंट = 1000, recreateContext = true: 1 9 1 सेकंड

उपरोक्त पहले परीक्षण में व्यवहार यह है कि प्रदर्शन बहुत गैर-रैखिक है और समय के साथ बेहद कम हो जाता है। ("कई घंटे" एक अनुमान है, मैंने कभी भी इस परीक्षा को समाप्त नहीं किया, मैंने 20 मिनट के बाद 50,000 इकाइयों को रोक दिया।) यह गैर-रैखिक व्यवहार अन्य सभी परीक्षणों में इतना महत्वपूर्ण नहीं है।


एंटिटी फ्रेमवर्क का उपयोग करने और एक यथार्थवादी उदाहरण पर एसकब्लूल्ककॉपी क्लास का उपयोग करने के बीच एक प्रदर्शन तुलना यहां दी गई है: SQL सर्वर डेटाबेस में जटिल ऑब्जेक्ट्स को सम्मिलित करने के लिए कैसे करें

जैसा कि अन्य ने पहले ही जोर दिया है, ओआरएम का उपयोग थोक संचालन में नहीं किया जाना है। वे लचीलापन, चिंताओं को अलग करने और अन्य लाभ प्रदान करते हैं, लेकिन थोक संचालन (थोक पढ़ने को छोड़कर) उनमें से एक नहीं हैं।


एक संग्रहीत प्रक्रिया का उपयोग करने का प्रयास करें जो उस डेटा का एक्सएमएल प्राप्त करेगा जिसे आप सम्मिलित करना चाहते हैं।


क्या आपने कभी पृष्ठभूमि कार्यकर्ता या कार्य के माध्यम से सम्मिलित करने का प्रयास किया है?

मेरे मामले में, मैं 7760 रजिस्टरों को सम्मिलित कर रहा हूं, जो 182 विभिन्न तालिकाओं में विदेशी कुंजी संबंधों (नेविगेशनप्रॉपर्टीज द्वारा) में वितरित किए गए हैं।

कार्य के बिना, इसमें 2 मिनट लग गए। एक कार्य के भीतर (कार्य। Task.Factory.StartNew(...) ), इसमें 15 सेकंड लग गए।

संदर्भ में सभी इकाइयों को जोड़ने के बाद मैं केवल SaveChanges() कर रहा SaveChanges() । (डेटा अखंडता सुनिश्चित करने के लिए)


जैसा कि अन्य लोगों ने कहा है कि यदि आप वास्तव में अच्छा सम्मिलित प्रदर्शन चाहते हैं तो एसकब्लूल्ककॉपी ऐसा करने का तरीका है।

यह लागू करने के लिए थोड़ा बोझिल है लेकिन पुस्तकालय हैं जो इससे आपकी मदद कर सकते हैं। वहां कुछ बाहर हैं लेकिन मैं इस समय अपनी खुद की लाइब्रेरी को शर्मिंदा कर दूंगा: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

आपको केवल एक ही कोड चाहिए:

 using (var db = new YourDbContext())
 {
     EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
 }

तो यह कितना तेज़ है? कहना मुश्किल है क्योंकि यह कई कारकों, कंप्यूटर प्रदर्शन, नेटवर्क, ऑब्जेक्ट साइज इत्यादि पर निर्भर करता है। मैंने जो प्रदर्शन परीक्षण किए हैं, सुझाव देते हैं कि 25k इकाइयों को स्थानीयहोस्ट पर मानक तरीके से लगभग 10s में डाला जा सकता है यदि आप अपनी ईएफ कॉन्फ़िगरेशन को अनुकूलित करते हैं अन्य उत्तरों में उल्लिखित। EFUtilities के साथ जो लगभग 300ms लेता है। इससे भी दिलचस्प बात यह है कि मैंने इस विधि का उपयोग करते हुए लगभग 15 सेकंड में लगभग 3 मिलियन इकाइयों को बचाया है, प्रति सेकंड लगभग 200k इकाइयों का औसत।

यदि आपको रिलीज़ किए गए डेटा को सम्मिलित करने की आवश्यकता है तो एक समस्या है। यह उपरोक्त विधि का उपयोग कर एसक्यूएल सर्वर में प्रभावी ढंग से किया जा सकता है लेकिन इसके लिए आपको एक आईडी पीढ़ी की रणनीति की आवश्यकता होती है जो आपको माता-पिता के लिए ऐप-कोड में आईडी उत्पन्न करने देती है ताकि आप विदेशी कुंजी सेट कर सकें। यह GUIDs या HiLo id generation जैसे कुछ का उपयोग करके किया जा सकता है।


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

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

public partial class YourEntities : DbContext
{
    public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            conn.Open();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            await bulkCopy.WriteToServerAsync(table);
        }
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            conn.Open();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            bulkCopy.WriteToServer(table);
        }
    }

    public string GetTableName(Type type)
    {
        var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
        var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

        var entityType = metadata
                .GetItems<EntityType>(DataSpace.OSpace)
                .Single(e => objectItemCollection.GetClrType(e) == type);

        var entitySet = metadata
            .GetItems<EntityContainer>(DataSpace.CSpace)
            .Single()
            .EntitySets
            .Single(s => s.ElementType.Name == entityType.Name);

        var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
                .Single()
                .EntitySetMappings
                .Single(s => s.EntitySet == entitySet);

        var table = mapping
            .EntityTypeMappings.Single()
            .Fragments.Single()
            .StoreEntitySet;

        return (string)table.MetadataProperties["Table"].Value ?? table.Name;
    }
}

आप इसका उपयोग किसी भी संग्रह के खिलाफ कर सकते हैं जो आईनेमेरेबल से प्राप्त होता है, जैसे कि:

await context.BulkInsertAllAsync(items);

मेरे ज्ञान के अनुसार विशाल आवेषण के प्रदर्शन को बढ़ाने के लिए no BulkInsert में no BulkInsert प्रविष्टि नहीं है।

इस परिदृश्य में आप अपनी समस्या का समाधान करने के लिए ADO.net में documentation साथ जा सकते हैं


मैं एडम रैकीस से सहमत हूं। SqlBulkCopy एक डेटा स्रोत से दूसरे डेटा में थोक रिकॉर्ड स्थानांतरित करने का सबसे तेज़ तरीका है। मैंने 20K रिकॉर्ड कॉपी करने के लिए इसका इस्तेमाल किया और इसमें 3 सेकंड से भी कम समय लगा। नीचे दिए गए उदाहरण पर एक नज़र डालें।

public static void InsertIntoMembers(DataTable dataTable)
{           
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Members";
                sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
                sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
                sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
                sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
                sqlBulkCopy.ColumnMappings.Add("Email", "Email");

                sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
                sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
                sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
                sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
                sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");

                sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
                sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");

                sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }

    }
}

मैंने ऊपर @ स्लुमा के उदाहरण का एक सामान्य विस्तार किया है;

public static class DataExtensions
{
    public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator)
    {
        context.Set(typeof(T)).Add((T)entity);

        if (count % commitCount == 0)
        {
            context.SaveChanges();
            if (recreateContext)
            {
                context.Dispose();
                context = contextCreator.Invoke();
                context.Configuration.AutoDetectChangesEnabled = false;
            }
        }
        return context;
    }
}

उपयोग:

public void AddEntities(List<YourEntity> entities)
{
    using (var transactionScope = new TransactionScope())
    {
        DbContext context = new YourContext();
        int count = 0;
        foreach (var entity in entities)
        {
            ++count;
            context = context.AddToContext<TenancyNote>(entity, count, 100, true,
                () => new YourContext());
        }
        context.SaveChanges();
        transactionScope.Complete();
    }
}

यह संयोजन गति को काफी अच्छी तरह बढ़ाता है।

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

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

अर्थात।

insert into some_staging_table using Entity Framework.

-- Single insert into main table (this could be a tiny stored proc call)
insert into some_main_already_large_table (columns...)
   select (columns...) from some_staging_table
truncate table some_staging_table

SqlBulkCopy उपयोग करें:

void BulkInsert(GpsReceiverTrack[] gpsReceiverTracks)
{
    if (gpsReceiverTracks == null)
    {
        throw new ArgumentNullException(nameof(gpsReceiverTracks));
    }

    DataTable dataTable = new DataTable("GpsReceiverTracks");
    dataTable.Columns.Add("ID", typeof(int));
    dataTable.Columns.Add("DownloadedTrackID", typeof(int));
    dataTable.Columns.Add("Time", typeof(TimeSpan));
    dataTable.Columns.Add("Latitude", typeof(double));
    dataTable.Columns.Add("Longitude", typeof(double));
    dataTable.Columns.Add("Altitude", typeof(double));

    for (int i = 0; i < gpsReceiverTracks.Length; i++)
    {
        dataTable.Rows.Add
        (
            new object[]
            {
                    gpsReceiverTracks[i].ID,
                    gpsReceiverTracks[i].DownloadedTrackID,
                    gpsReceiverTracks[i].Time,
                    gpsReceiverTracks[i].Latitude,
                    gpsReceiverTracks[i].Longitude,
                    gpsReceiverTracks[i].Altitude
            }
        );
    }

    string connectionString = (new TeamTrackerEntities()).Database.Connection.ConnectionString;
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var transaction = connection.BeginTransaction())
        {
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = dataTable.TableName;
                foreach (DataColumn column in dataTable.Columns)
                {
                    sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
                }

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
    }

    return;
}

All the solutions written here don't help because when you do SaveChanges(), insert statements are sent to database one by one, that's how Entity works.

And if your trip to database and back is 50 ms for instance then time needed for insert is number of records x 50 ms.

You have to use BulkInsert, here is the link: https://efbulkinsert.codeplex.com/

I got insert time reduced from 5-6 minutes to 10-12 seconds by using it.


Use stored procedure that takes input data in form of xml to insert data.

From your c# code pass insert data as xml.

eg in c#, syntax would be like this:

object id_application = db.ExecuteScalar("procSaveApplication", xml)





entity-framework