Varlık Çerçevesine Eklemenin En Hızlı Yolu


682

Entity Framework'e eklemenin en hızlı yolunu arıyorum.

Ben aktif bir TransactionScope olduğu ve ekleme büyük (4000+) senaryo nedeniyle bunu soruyorum. Potansiyel olarak 10 dakikadan uzun sürebilir (işlemlerin varsayılan zaman aşımı) ve bu, işlemin tamamlanmamasına neden olur.


1
Şu anda nasıl yapıyorsun?
Dustin Laine

TransactionScope'u oluşturma, DBContext'i örnekleme, Bağlantıyı açma ve her biri için eklemeleri ve SavingChanges'i (her kayıt için) yapan bir deyimde, NOT: TransactionScope ve DBContext deyimleri kullanıyor ve son olarak bağlantıyı kapatıyorum blok
Bongo Sharp

Referans için başka bir cevap: stackoverflow.com/questions/5798646/…
Ladislav Mrnka

2
Bir SQL veritabanına eklemenin en hızlı yolu EF içermez. AFAIK BCP'sinden sonra TVP + Birleştir / Ekle.
StingyJack

1
Yorumları okuyacaklar için: En uygulanabilir, modern cevap burada.
Tanveer Badar

Yanıtlar:


986

Sorunuza yaptığınız yorumlardaki yorumunuza:

"... SavingChanges ( her kayıt için ) ..."

Bu yapabileceğiniz en kötü şey! SaveChanges()Her kayıt için çağrı yapılması toplu ekleri son derece yavaşlatır. Büyük olasılıkla performansı artıracak birkaç basit test yapardım:

  • SaveChanges()TÜM kayıtlardan sonra bir kez arayın .
  • SaveChanges()Örneğin 100 kayıttan sonra arayın .
  • SaveChanges()Örneğin 100 kayıttan sonra arayın ve içeriği atın ve yeni bir kayıt oluşturun.
  • Değişiklik algılamayı devre dışı bırak

Toplu ekler için şu şekilde bir desenle çalışıyorum ve deniyorum:

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;
}

DB 560.000 varlıkları (9 skaler özellikleri, navigasyon özellikleri yok) ekleyen bir test programı var. Bu kod ile 3 dakikadan daha kısa sürede çalışır.

Performans için SaveChanges()"çok" kayıttan sonra (100 veya 1000 civarında "çok") aramak önemlidir . SaveChanges'ten sonra içeriği elden çıkarma ve yeni bir tane oluşturma performansını da geliştirir. Bu, bağlamı tüm girişlerden temizler, SaveChangesbunu yapmaz, varlıklar hala duruma bağlamda bağlıdır Unchanged. Bağlamadaki adım adım ekleme işlemini yavaşlatan bağlamdaki bağlı varlıkların büyüyen boyutudur. Bu nedenle, bir süre sonra temizlemek yardımcı olur.

560000 objelerim için birkaç ölçüm:

  • commitCount = 1, leisureteContext = false: birçok saat (Şu anki prosedürünüz bu)
  • commitCount = 100, rekorContext = false: 20 dakikadan fazla
  • commitCount = 1000, rekorContext = yanlış: 242 sn
  • commitCount = 10000, rekorContext = yanlış: 202 sn
  • commitCount = 100000, rekorContext = yanlış: 199 sn
  • commitCount = 1000000, leisureteContext = false: bellek yetersiz özel durumu
  • commitCount = 1, leisureteContext = true: 10 dakikadan fazla
  • commitCount = 10, rekorContext = true: 241 saniye
  • commitCount = 100, rekorContext = true: 164 saniye
  • commitCount = 1000, leisureteContext = true: 191 saniye

Yukarıdaki ilk testteki davranış, performansın çok doğrusal olmaması ve zamanla aşırı derecede azalmasıdır. ("Birçok saat" bir tahmindir, bu testi hiç bitirmedim, 20 dakika sonra 50.000 birimde durdum.) Bu doğrusal olmayan davranış diğer tüm testlerde o kadar önemli değildir.


89
@Bongo Sharp: DbContext AutoDetectChangesEnabled = false;üzerinde ayarlamayı unutmayın . Ayrıca büyük bir ek performans etkisi vardır: stackoverflow.com/questions/5943394/…
Slauma

6
Evet, sorun Entity Framework 4 kullanıyorum ve AutoDetectChangesEnabled 4.1'in bir parçası, yine de, performans testi yaptım ve İNANILMAZ SONUÇLAR vardı, 00:12:00 - 00:00:22 SavinChanges her varlık olverload yapıyordu ... answare için TEŞEKKÜRLER! aradığım şey bu
Bongo Sharp

10
Context.Configuration.AutoDetectChangesEnabled = false; ucu, bir yapar kocaman bir fark.
douglaz

1
@ dahacker89: Doğru sürüm EF> = 4.1 ve kullanıyorsunuz DbContext, DEĞİL ObjectContext?
Slauma

3
@ dahacker89: Sorununuz için belki daha fazla ayrıntı içeren ayrı bir soru oluşturmanızı öneririm. Burada neyin yanlış olduğunu anlayamıyorum.
Slauma

176

Bu kombinasyon hızı yeterince arttırır.

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

46
ValidateOnSaveEnabled'ı körü körüne devre dışı bırakmayın, bu davranışa bağlı olabilirsiniz ve çok geç olana kadar fark etmeyebilirsiniz. Sonra tekrar kodun başka bir yerinde doğrulama yapıyor olabilirsiniz ve EF doğrulaması yine gerekli değildir.
Jeremy Cook

1
Test tasarrufumda 20.000 satır 101 saniyeden 88 saniyeye düştü. Çok fazla değil ve etkileri nelerdir.
AH.

27
@JeremyCook Sanırım elde etmeye çalıştığınız şey, bu özelliklerin varsayılan değerlerinden (performans iyileştirmesinin yanı sıra) değiştirilmesinin olası sonuçlarını açıklarsa, bu cevabın çok daha iyi olacağını düşünüyorum. Katılıyorum.
pseudocoder

1
Bağlamda kayıtları güncelliyorsanız, DetectChanges () 'i açıkça çağırmanız gerekecek olsa da, bu benim için çalıştı
hillstuk

2
Bunlar devre dışı bırakılabilir ve daha sonra bir dene-
sonla bloğuyla

98

En hızlı yol , geliştirdiğim toplu ekleme uzantısını kullanmak olurdu

not: bu ticari bir üründür, ücretsiz değildir

Maksimum performans elde etmek için SqlBulkCopy ve özel datareader kullanır. Sonuç olarak, normal kesici uç veya AddRange kullanmaktan 20 kat daha hızlıdır EntityFramework.BulkInsert vs EF AddRange

kullanımı son derece basit

context.BulkInsert(hugeAmountOfEntities);

10
Hızlı ama sadece bir hiyerarşinin üst katmanı.
CAD bloke

66
Ücretsiz değil.
Amir Saniyan

72
Reklamlar daha akıllı hale geliyor ... Bu ücretli bir üründür ve serbest çalışanlar için çok pahalıdır. Dikkatli olun!
JulioQc

35
1 yıllık destek ve yükseltmeler için 600 USD? Aklını mı kaçırdın?
Camilo Terevinto

7
im artık ürünün sahibi değilim
maxlego

83

Bunun için kullanmaya bakmalısınız System.Data.SqlClient.SqlBulkCopy. İşte belgeler ve elbette çevrimiçi çok sayıda öğretici var.

Üzgünüz, EF'in istediğinizi yapmasını sağlamak için basit bir yanıt aradığınızı biliyorum, ancak toplu işlemler gerçekten ORM'lerin amaçladığı şey değildir.


1
Bu araştırma sırasında birkaç kez SqlBulkCopy içine koştum, ama daha masaya ekler için daha yönlendirilmiş gibi görünüyor, ne yazık ki kolay çözümler beklemiyordum, ama daha ziyade performans ipuçları, örneğin Devleti yönetmek gibi bağlantıyı manuel olarak ayarlayın, EF'in sizin için yapmasına izin vermek
Bongo Sharp

7
Uygulamamdan büyük miktarda veri eklemek için SqlBulkCopy kullandım. Temelde sonra geçmesi, bir DataTable oluşturmak doldurmak zorunda olduğu BulkCopy için. DataTable'ınızı kurarken birçoğu var (çoğu unuttum, ne yazık ki), ama iyi çalışmalı
Adam Rackis

2
Kavramın kanıtını yaptım ve söz verdiğim gibi, gerçekten hızlı çalışıyor, ancak EF kullanmamın nedenlerinden biri, ilişkisel verilerin eklenmesi daha kolay olduğundan, örneğin zaten ilişkisel veriler içeren bir varlık ekliyorsam , bunu da ekleyecek, hiç bu senaryoya girdiniz mi? Teşekkürler!
Bongo Sharp

2
Ne yazık ki bir DBMS içine nesne web eklemek gerçekten BulkCopy yapacak bir şey değildir. EF gibi bir ORM'nin yararı budur, maliyeti yüzlerce benzer nesne grafiğini verimli bir şekilde yapmak için ölçeklendirilmemesidir.
Adam Rackis

2
SqlBulkCopy ham hıza ihtiyacınız varsa veya bu eki yeniden çalıştırıyorsanız kesinlikle gitmenin yoludur. Daha önce onunla birkaç milyon kayıt ekledim ve son derece hızlı. Bununla birlikte, bu eki yeniden çalıştırmanız gerekmedikçe, EF'yi kullanmak daha kolay olabilir.
Neil

49

Adam Rackis ile aynı fikirdeyim. SqlBulkCopytoplu kayıtları bir veri kaynağından diğerine aktarmanın en hızlı yoludur. Bunu 20K kayıtları kopyalamak için kullandım ve 3 saniyeden az sürdü. Aşağıdaki örneğe bir göz atın.

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();
        }

    }
}

1
Bu yazı sağlanan çözümlerin çoğunu denedim ve SqlBulkCopy çok hızlı oldu. Saf EF 15 dakika sürdü, ancak çözüm ve SqlBulkCopy bir karışımı ile 1.5 dakika aşağı almak mümkün! Bu 2 milyon kayıtla oldu! Herhangi bir DB dizin optimizasyonu olmadan.
jonas

Liste DataTable'dan daha kolaydır. AsDataReader()Bu cevapta açıklanan bir uzantı yöntemi var: stackoverflow.com/a/36817205/1507899
RJB

Ama bu sadece üst Varlık için ilişkisel değil
Zahid Mustafa

1
@ZahidMustafa: evet. BulkInsert yapıyor, Bulk-Analysis-And-Relation-Tracing-Object-Graphs .. İlişkileri kapsamak istiyorsanız, ekleme sırasını analiz edip belirlemeniz ve daha sonra bireysel seviyeleri toplu olarak eklemeniz ve belki bazı anahtarları güncellemeniz gerekir. gerekli ve hızlı özel uyarlanmış çözüm alacaksınız. Ya da, bunu yapmak için EF'e güvenebilirsiniz, yanınızda çalışma yok, ancak çalışma zamanında daha yavaş.
Temmuz 17'de quetzalcoatl

23

EF kullanarak toplu eklerin nasıl yapılacağı hakkında bu makaleyi tavsiye ederim.

Varlık Çerçevesi ve yavaş toplu INSERT'ler

Bu alanları araştırıyor ve performansı karşılaştırıyor:

  1. Varsayılan EF (30.000 kayıt eklemeyi tamamlamak için 57 dakika)
  2. ADO.NET Koduyla değiştirme ( aynı 30.000 için 25 saniye )
  3. Bağlam Şişirme - Her İş Birimi için yeni bir bağlam kullanarak etkin Bağlam Grafiğini küçük tutun (aynı 30.000 ek 33 saniye sürer)
  4. Büyük Listeler - AutoDetectChangesEnabled'ı kapatın (zamanı yaklaşık 20 saniyeye düşürür)
  5. Dozajlama (16 saniyeye kadar)
  6. DbTable.AddRange () - (performans 12 aralıktadır)

21

burada daha önce bahsedilmedi gibi ben EFC tavsiye etmek istiyorum.BulkExtensions burada

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);

1
Bu öneriyi ikinci olarak belirttim. Birçok homebrew çözümünü denedikten sonra bu ekimi 50 saniyeden 1 saniyeye indirdi. Ve MIT lisansı dahil etmek çok kolay.
SouthShoreAK

6.x için boşuna
Alok

bu 10'dan fazla varlık varsa AddRange kullanmaktan daha fazla performans
Jackal

5
10.000 insert 9 dakikadan 12 saniyeye çıktı. Bu daha fazla ilgiyi hak ediyor!
callisto

2
Kabul edilen cevapları değiştirmenin herhangi bir yolu varsa, bu şimdi kabul edilen modern cevap olmalıdır. Ve keşke EF ekibi bunu kutusundan çıkarsa.
Tanveer Badar

18

Slauma'nın cevabını araştırdım (bu harika, fikir adamı için teşekkürler) ve optimal hıza ulaşana kadar parti boyutunu küçültdüm. Slauma sonuçlarına bakıldığında:

  • commitCount = 1, leisureteContext = true: 10 dakikadan fazla
  • commitCount = 10, rekorContext = true: 241 saniye
  • commitCount = 100, rekorContext = true: 164 saniye
  • commitCount = 1000, leisureteContext = true: 191 saniye

1'den 10'a ve 10'dan 100'e geçerken hız artışı olduğu görülebilir, ancak 100'den 1000'e ekleme hızı tekrar düşer.

Bu nedenle, toplu iş boyutunu 10 ile 100 arasında bir yere değer düşürdüğünüzde neler olduğuna odaklandım ve işte sonuçlarım (farklı satır içerikleri kullanıyorum, bu yüzden zamanlarım farklı değerde):

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

Sonuçlarıma dayanarak, gerçek optimum toplu iş boyutu için 30 civarındadır. Hem 10 hem de 100'den az. Sorun şu ki, neden 30'un en uygun olduğu hakkında hiçbir fikrim yok, bunun için mantıklı bir açıklama bulamazdım.


2
Ben 30 en uygun Postrges ve saf SQL (EF değil SQL bağlıdır) ile aynı buldum.
Kamil Gareev

Deneyimlerim, farklı bağlantı hızı ve sıra boyutu için optimumun farklı olması. Hızlı bağlantı ve küçük sıralar için optimum> 200 sıra bile olabilir.
jing

18

Diğer insanlar söylediler SqlBulkCopy gerçekten iyi bir performans istiyorsanız bunu yapmak için bir yoldur.

Uygulamak biraz hantal ama size yardımcı olabilecek kütüphaneler var. Orada bir kaç tane var ama bu sefer kendi kütüphanemi utanmadan fişleyeceğim: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

İhtiyacınız olan tek kod:

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

Peki ne kadar hızlı? Söylemek çok zor çünkü birçok faktöre, bilgisayar performansına, ağa, nesne boyutuna vb. Bağlı. standart yol sizi sevdiği EF yapılandırmasını optimize IF localhost üzerinde diğer cevaplarda belirtilmiştir. Yaklaşık 300 ms süren EFUtilities ile. Daha da ilginç olanı, bu yöntemi kullanarak 15 saniyenin altında yaklaşık 3 milyon varlığı kurtardım.

Tek sorun, ilgili verileri eklemeniz gerektiğinde. Bu, yukarıdaki yöntemi kullanarak sql sunucusuna etkili bir şekilde yapılabilir, ancak yabancı anahtarları ayarlayabilmeniz için ebeveynin uygulama kodunda kimlikler oluşturmanıza izin veren bir Kimlik oluşturma stratejisine sahip olmanızı gerektirir. Bu GUID'ler veya HiLo id üretimi gibi bir şey kullanılarak yapılabilir.


İyi çalışıyor. Sözdizimi biraz ayrıntılı olsa da. Her statik yönteme EFBatchOperationgeçmek DbContextyerine, içinden geçtiğiniz bir yapıcıya sahip olmanın daha iyi olacağını düşünün . Jenerik versiyonları InsertAllve UpdateAllotomatik benzer koleksiyonu, bulmak DbContext.Set<T>çok iyi olurdu.
Ocak 15

Teşekkür etmek için sadece hızlı bir yorum! Bu kod, 1,5 saniyede 170 bin kayıt kaydetmeme izin verdi! Sudan denediğim diğer yöntemleri tamamen esiyor.
Tom Glenn

@Mikael Bir konu kimlik alanlarıyla ilgileniyor. Kimlik eklemeyi henüz etkinleştirmenin bir yolu var mı?
Joe Phillips

1
EntityFramework.BulkInsert'in aksine, bu kütüphane serbest kaldı. +1
Rudey

14

Dispose()bağlamda Add()önceden yüklenmiş diğer varlıklara (örn. gezinme özellikleri) güvendiğiniz varlıklar sorun yaratır

Aynı performansı elde etmek için bağlamımı küçük tutmak için benzer bir kavram kullanıyorum

Ama Dispose()bağlam yerine ve yeniden yaratmak yerine, zaten var olan varlıkları ayırıyorumSaveChanges()

public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class {

const int CommitCount = 1000; //set your own best performance number here
int currentCount = 0;

while (currentCount < entities.Count())
{
    //make sure it don't commit more than the entities you have
    int commitCount = CommitCount;
    if ((entities.Count - currentCount) < commitCount)
        commitCount = entities.Count - currentCount;

    //e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext
    for (int i = currentCount; i < (currentCount + commitCount); i++)        
        _context.Entry(entities[i]).State = System.Data.EntityState.Added;
        //same as calling _context.Set<TEntity>().Add(entities[i]);       

    //commit entities[n to n+999] to database
    _context.SaveChanges();

    //detach all entities in the context that committed to database
    //so it won't overload the context
    for (int i = currentCount; i < (currentCount + commitCount); i++)
        _context.Entry(entities[i]).State = System.Data.EntityState.Detached;

    currentCount += commitCount;
} }

catch ile deneyin ve TrasactionScope()ihtiyacınız varsa, kodu temiz tutmak için burada göstermeyin


1
Bu, Entity Framework 6.0 kullanarak eki (AddRange) yavaşlattı. 20.000 satır eklemek yaklaşık 101 saniyeden 118 saniyeye çıktı.
AH.

1
@Stephen Ho: Bağlamımı atmaktan da kaçınmaya çalışıyorum. Bunun bağlamı yeniden oluşturmaktan daha yavaş olduğunu anlayabiliyorum, ancak bunu bağlamı yeniden oluşturmaktan ziyade bir commitCount seti ile yeterince hızlı bulduğunuzu bilmek istiyorum.
Öğrenci

@ Öğrenci: Bağlamı yeniden oluşturmaktan daha hızlı olduğunu düşünüyorum. Ama sonunda hatırlamıyorum çünkü sonunda SqlBulkCopy kullanmaya geçti.
Stephen Ho

Bu tekniği kullanmak zorunda kaldım, çünkü garip bir nedenden ötürü, while döngüsünün ikinci geçişinde gerçekleşen bazı izleme işlemleri kaldı, ancak her şeyi bir kullanım ifadesine sarılıp DbContext üzerinde bile Dispose () olarak adlandırdım. . Bağlama (2. geçişte) eklediğimde, bağlam kümesi sayısı sadece bir yerine 6'ya atlardı. İsteğe bağlı olarak eklenen diğer öğeler, while döngüsünün ilk geçişine zaten eklenmişti, bu nedenle SaveChanges çağrısı ikinci geçişte başarısız olurdu (bariz nedenlerle).
Hallmanac

9

Bunun çok eski bir soru olduğunu biliyorum, ama burada bir adam EF ile toplu ekleme kullanmak için bir uzatma yöntemi geliştirdiğini söyledi ve kontrol ettiğimde, kütüphanenin bugün 599 dolara mal olduğunu keşfettim (bir geliştirici için). Belki de tüm kütüphane için mantıklıdır, ancak sadece toplu ekleme için bu çok fazladır.

İşte yaptığım çok basit bir uzatma yöntemi. Ben ilk veritabanı ile çift üzerinde kullanın (ilk kod ile test etmeyin, ama aynı çalışır düşünüyorum). YourEntitiesBağlamınızın adıyla değiştirin :

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

            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;
    }
}

Bunu, miras alınan herhangi bir koleksiyona karşı kullanabilirsiniz IEnumerable, örneğin:

await context.BulkInsertAllAsync(items);

lütfen örnek kodunuzu girin. bulkCopy
Seabizkit

1
Zaten burada:await bulkCopy.WriteToServerAsync(table);
Guilherme

Belki ben açık değildi, senin yazma, sen aslında her iki yöntemde SqlBulkCopy lib kullanmak hiçbir 3. bölüm lib gerekli, yani demek istedi bir uzatma yaptığını öneririz. Bu tamamen SqlBulkCopy, neden bulkCopy nereden geldiğini sorduğunda, onun üstüne bir uzatma lib yazdı bir uzantı lib dayanır. Sadece burada nasıl SqlBulkCopy lib kullanılan olduğunu söylemek daha anlamlı olur.
Seabizkit

zaman uyumsuz sürümünde conn.OpenAsync kullanmalısınız
Robert

6

Eklemek istediğiniz verilerin XML'ini alacak bir Saklı Yordam kullanmaya çalışın .


9
XML olarak saklamak istemiyorsanız, verileri XML olarak iletmeniz gerekmez. SQL 2008'de tablo değerli parametre kullanabilirsiniz.
Ladislav Mrnka

Bunu açıklığa kavuşturmadım ama SQL 2005'i de desteklemeliyim
Bongo Sharp

4

Yukarıdaki @Slauma örneğinin genel bir uzantısını yaptım;

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;
    }
}

Kullanımı:

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();
    }
}

4

Entity Framework'e eklemenin en hızlı yolunu arıyorum

Toplu Ek'i destekleyen bazı üçüncü taraf kütüphaneleri vardır:

  • Z.EntityFramework.Eextensions ( Önerilen )
  • EFUtilities
  • EntityFramework.BulkInsert

Bkz: Entity Framework Toplu Ekleme kitaplığı

Bir toplu ekleme kitaplığı seçerken dikkatli olun. Yalnızca Entity Framework Uzantıları her türlü ilişkilendirmeyi ve mirası destekler ve hala desteklenen tek uzantıdır.


Feragatname : Entity Framework Uzantılarının sahibiyim

Bu kütüphane, senaryolarınız için ihtiyacınız olan tüm toplu işlemleri gerçekleştirmenizi sağlar:

  • Toplu Kaydetme
  • Toplu Ekleme
  • Toplu Silme
  • Toplu güncelleme
  • Toplu Birleştirme

Misal

// 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;
});

19
bu harika bir uzantıdır, ancak ücretsiz değildir .
Okan Kocyigit

2
Bu cevap oldukça iyi ve EntityFramework.BulkInsert , 1,5 saniyede 15K satırlık toplu ekleme gerçekleştirir, Windows Hizmeti gibi dahili bir işlem için oldukça iyi çalışır.
Rahip Cortes

4
Evet, toplu ekleme için 600 $. Tamamen buna değer.
eocron

1
@eocron Eğer ticari olarak kullanırsanız buna değer. 600 $ ile bana çok fazla mal olacak bina kendimi bina üzerinde saat harcamak zorunda değilsiniz bir şey için 600 $ ile herhangi bir sorun görmüyorum. Evet para maliyeti ama benim saatlik ücret bakarak iyi harcama para!
Jordy van Eijk

3

Kullanım 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;
}

3

Bir listeyi kaydetmenin en hızlı yollarından biri aşağıdaki kodu uygulamanız gerekir

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

AutoDetectChangesEnabled = yanlış

Add, AddRange & SaveChanges: Değişiklikleri algılamaz.

ValidateOnSaveEnabled = false;

Değişiklik izleyiciyi algılamıyor

Nuget eklemelisin

Install-Package Z.EntityFramework.Extensions

Şimdi aşağıdaki kodu kullanabilirsiniz

var context = new MyContext();

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

context.BulkInsert(list);
context.BulkSaveChanges();

Güncelleme için örnek Kodunuzu nasıl kullanabilirim?
AminGolmahalle

4
Z kütüphanesi ücretsiz değil
SHADOW.NET

3

SqlBulkCopy süper hızlı

Bu benim uygulamam:

// at some point in my calling code, I will call:
var myDataTable = CreateMyDataTable();
myDataTable.Rows.Add(Guid.NewGuid,tableHeaderId,theName,theValue); // e.g. - need this call for each row to insert

var efConnectionString = ConfigurationManager.ConnectionStrings["MyWebConfigEfConnection"].ConnectionString;
var efConnectionStringBuilder = new EntityConnectionStringBuilder(efConnectionString);
var connectionString = efConnectionStringBuilder.ProviderConnectionString;
BulkInsert(connectionString, myDataTable);

private DataTable CreateMyDataTable()
{
    var myDataTable = new DataTable { TableName = "MyTable"};
// this table has an identity column - don't need to specify that
    myDataTable.Columns.Add("MyTableRecordGuid", typeof(Guid));
    myDataTable.Columns.Add("MyTableHeaderId", typeof(int));
    myDataTable.Columns.Add("ColumnName", typeof(string));
    myDataTable.Columns.Add("ColumnValue", typeof(string));
    return myDataTable;
}

private void BulkInsert(string connectionString, DataTable dataTable)
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        SqlTransaction transaction = null;
        try
        {
            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();
        }
        catch (Exception)
        {
            transaction?.Rollback();
            throw;
        }
    }
}

3

[2019 Güncellemesi] EF Core 3.1

Yukarıda söylenenleri takiben, EF Core'da AutoDetectChangesEnabled devre dışı bırakılması mükemmel bir şekilde çalıştı: ekleme süresi 100'e bölündü (birkaç dakikadan birkaç saniyeye, çapraz tablo ilişkilerine sahip 10k kayıt)

Güncellenen kod:

  context.ChangeTracker.AutoDetectChangesEnabled = false;
            foreach (IRecord record in records) {
               //Add records to your database        
            }
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable

2

İşte gerçekçi bir örnekte Entity Framework ile SqlBulkCopy sınıfını kullanma arasında bir performans karşılaştırması: SQL Server veritabanına karmaşık nesneleri toplu olarak ekleme

Diğerlerinin de vurguladığı gibi, ORM'lerin toplu operasyonlarda kullanılması amaçlanmamıştır. Esneklik, kaygıların ayrılması ve diğer faydalar sunarlar, ancak toplu işlemler (toplu okuma hariç) bunlardan biri değildir.


2

Başka bir seçenek de Nuget'te bulunan SqlBulkTools'u kullanmaktır. Kullanımı çok kolay ve bazı güçlü özelliklere sahip.

Misal:

var bulk = new BulkOperations();
var books = GetBooks();

using (TransactionScope trans = new TransactionScope())
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager
    .ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
    {
        bulk.Setup<Book>()
            .ForCollection(books)
            .WithTable("Books") 
            .AddAllColumns()
            .BulkInsert()
            .Commit(conn);
    }

    trans.Complete();
}

Daha fazla örnek ve gelişmiş kullanım için belgelere bakın . Feragatname: Bu kütüphanenin yazarıyım ve herhangi bir görüş benim düşüncemde.


2
Bu proje hem NuGet hem de GitHub'dan silindi.
17'de

1

Bildiğim kadarıyla gereğince orada no BulkInsertiçinde EntityFrameworkdev ekler performansını artırmak için.

Bu senaryoda, gidebilirsiniz SqlBulkCopy içinde ADO.netsorunu çözmek için


Bu sınıfa bir göz atıyordum, ama tablodan tabloya eklemelere daha çok yönelmiş gibi görünüyor değil mi?
Bongo Sharp

Ne demek istediğinizden emin değilim, aşırı yüklü WriteToServerbir DataTable.
Blindy,

Hayır .Net nesnelerinden SQL'e de ekleyebilirsiniz.Ne arıyorsunuz?
anishMarokey

TransactionScope bloğuna potansiyel olarak binlerce kayıt eklemenin bir yolu
Bongo Sharp

Kullanabileceğiniz .Net TransactionScope technet.microsoft.com/en-us/library/bb896149.aspx
anishMarokey

1

Hiç bir arka plan çalışanı veya görevi eklemeye çalıştınız mı?

Benim durumumda, yabancı anahtar ilişkileri (NavigationProperties tarafından) ile 182 farklı tablolara dağıtılmış 7760 kayıtları ekleyerek im.

Görev olmadan, 2 buçuk dakika sürdü. Bir Görev ( Task.Factory.StartNew(...)) içinde 15 saniye sürdü.

Ben sadece SaveChanges()bağlama tüm varlıkları ekledikten sonra yapıyor . (veri bütünlüğünü sağlamak için)


2
Bağlam güvenli değil eminim. Tüm varlıkların kaydedildiğinden emin olmak için testleriniz var mı?
Danny Varod

Tüm varlık çerçevesi iplik güvenli hiç değil, ama ben sadece bağlam için nesneleri ekleyerek ve sonunda tasarruf biliyorum ... Onun mükemmel burada çalışıyor.
Rafael AMS

Yani, ana iş parçacığında DbContext.SaveChanges () çağırıyorsunuz, ancak içeriğe varlık ekleme arka plan iş parçacığında gerçekleştirilir, değil mi?
Prokurors

1
Evet, iş parçacıklarının içine veri ekleyin; herkesin bitmesini bekleyin;
Rafael AMS

Bu yolun tehlikeli ve hatalara yatkın olduğunu düşünsem de, çok ilginç buluyorum.
Öğrenci

1

Burada yazılan tüm çözümler yardımcı olmaz çünkü SaveChanges () yöntemini kullandığınızda, insert deyimleri birer birer veritabanına gönderilir, Entity böyle çalışır.

Örneğin, veritabanına ve geri dönüş yolculuğunuz 50 ms ise, ekleme için gereken süre x 50 ms kayıt sayısıdır.

BulkInsert kullanmak zorundasınız, işte link: https://efbulkinsert.codeplex.com/

Ekleme süresini kullanarak 5-6 dakikadan 10-12 saniyeye düşürdüm.



1

[POSTGRESQL İÇİN YENİ ÇÖZÜM] Hey, oldukça eski bir gönderi olduğunu biliyorum, ancak son zamanlarda benzer bir sorunla karşılaştım, ancak Postgresql kullanıyorduk. Oldukça zor olduğu ortaya çıkan etkili bulkinsert kullanmak istedim. Bu DB bunu yapmak için herhangi bir uygun ücretsiz kütüphane bulamadık. Sadece bu yardımcıyı buldum: https://bytefish.de/blog/postgresql_bulk_insert/ ve aynı zamanda Nuget'te. Entity Framework şekilde otomatik eşlenen özellikleri küçük bir haritacı yazdım:

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
        {
            var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
            var properties = typeof(T).GetProperties();
            foreach(var prop in properties)
            {
                var type = prop.PropertyType;
                if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
                    continue;
                switch (type)
                {
                    case Type intType when intType == typeof(int) || intType == typeof(int?):
                        {
                            helper = helper.MapInteger("\"" + prop.Name + "\"",  x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type stringType when stringType == typeof(string):
                        {
                            helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
                        {
                            helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
                        {
                            helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
                        {
                            helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
                        {
                            helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type guidType when guidType == typeof(Guid):
                        {
                            helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                }
            }
            return helper;
        }

Bunu şu şekilde kullanıyorum (Taahüt adlı bir varlığı vardı):

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));

İşlemle ilgili bir örnek gösterdim, ancak bağlamdan alınan normal bağlantı ile de yapılabilir. undertakingsToAdd, DB'ye bulkInsert yapmak istediğim normal varlık kayıtlarında numaralandırılabilir.

Birkaç saatlik araştırma ve denemeden sonra aldığım bu çözüm, çok daha hızlı ve nihayet kullanımı kolay ve ücretsiz olmasını beklediğiniz gibi! Bu çözümü sadece yukarıda belirtilen nedenlerle değil, aynı zamanda Postgresql'in kendisinde sorun yaşamadığım tek çözüm olduğu için kullanmanızı tavsiye ederim, diğer birçok çözüm örneğin SqlServer ile kusursuz çalışır.


0

İşin sırrı, aynı boş bir evreleme tablosuna eklemektir. Uçlar çabuk açılır. Sonra tek bir ana büyük tablonuza bundan kesici uç ekleyin. Ardından, bir sonraki grup için hazır olan hazırlama tablosunu kesin.

yani.

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

EF kullanarak, tüm kayıtlarınızı boş bir hazırlama tablosuna ekleyin. Sonra tek bir SQL komutunda ana (büyük ve yavaş) tabloya eklemek için SQL kullanın . Ardından hazırlama tablonuzu boşaltın. Zaten büyük bir tabloya çok fazla veri eklemenin çok hızlı bir yolu.
Simon Hughes

13
EF kullandığınızda, kayıtları hazırlama tablosuna ekleyin, bunu gerçekten EF ile denediniz mi? EF, her bir ek ile veritabanına ayrı bir çağrı yayınladığından, OP'nin kaçınmaya çalıştığı aynı isabeti göreceğinizden şüpheleniyorum. Hazırlama tablosu bu sorunu nasıl önler?
Jim Wooley

-1

Ancak, (+4000) 'den fazla kesici uç için saklı yordamı kullanmanızı öneririm. geçen süreyi ekledi. 20 "içine 11.788 satır ekledimresim açıklamasını buraya girin

bu kod

 public void InsertDataBase(MyEntity entity)
    {
        repository.Database.ExecuteSqlCommand("sp_mystored " +
                "@param1, @param2"
                 new SqlParameter("@param1", entity.property1),
                 new SqlParameter("@param2", entity.property2));
    }

-1

Veri eklemek için giriş verilerini xml biçiminde alan saklı yordamı kullanın.

C # kod geçişinizden xml olarak veri ekleyin.

örneğin, c # sözdizimi şöyle olur:

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

-7

Entity Framework'e kayıt ekleme hızını artırmak için bu tekniği kullanın. Burada kayıtları eklemek için basit bir saklı yordamı kullanın. Ve bu saklı yordamı yürütmek için Entity Framework .FromSql () yöntemini kullanın Ham SQL yürüten .

Saklı yordam kodu:

CREATE PROCEDURE TestProc
@FirstParam VARCHAR(50),
@SecondParam VARCHAR(50)

AS
  Insert into SomeTable(Name, Address) values(@FirstParam, @SecondParam) 
GO

Ardından, tüm 4000 kayıtlarınız arasında gezinin gezinin ve depolanan yürütmeyi yürüten Entity Framework kodunu ekleyin

prosedür her 100. döngüde zorlar.

Bunun için bu yordamı yürütmek için bir dize sorgusu oluşturmak, her kayıt kümesine ekleme devam edin.

Sonra döngünün 100'ün katlarında çalıştığını kontrol edin ve bu durumda kullanarak çalıştırın .FromSql().

Bu yüzden 4000 kayıt için sadece 4000/100 = 40 kez prosedürü yürütmem gerekiyor .

Aşağıdaki kodu kontrol edin:

string execQuery = "";
var context = new MyContext();
for (int i = 0; i < 4000; i++)
{
    execQuery += "EXEC TestProc @FirstParam = 'First'" + i + "'', @SecondParam = 'Second'" + i + "''";

    if (i % 100 == 0)
    {
        context.Student.FromSql(execQuery);
        execQuery = "";
    }
}

Bu etkili olabilir, ancak varlık çerçevesinin KULLANILMAMASINA eşdeğer olabilir. OP sorusu, Entity Framework bağlamında verimliliği nasıl en üst düzeye çıkaracağımızdı
kall2sollies
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.