Cara Tercepat Memasukkan dalam Kerangka Entitas


682

Saya mencari cara tercepat untuk memasukkan ke dalam Kerangka Entitas.

Saya bertanya ini karena skenario di mana Anda memiliki TransactionScope aktif dan penyisipan sangat besar (4000+). Ini berpotensi bertahan lebih dari 10 menit (batas waktu transaksi default), dan ini akan menyebabkan transaksi tidak lengkap.


1
Bagaimana kabarmu saat ini?
Dustin Laine

Membuat TransactionScope, instantiating DBContext, Membuka koneksi, dan dalam-untuk pernyataan masing-masing melakukan penyisipan dan SavingChanges (untuk setiap catatan), CATATAN: TransactionScope dan DBContext menggunakan pernyataan, dan saya menutup koneksi pada akhirnya blok
Bongo Sharp


2
Cara tercepat memasukkan ke dalam database SQL tidak melibatkan EF. AFAIK BCP-nya lalu TVP + Gabung / masukkan.
StingyJack

1
Bagi mereka yang akan membaca komentar: Paling berlaku, jawaban modern ada di sini.
Tanveer Badar

Jawaban:


986

Untuk komentar Anda di komentar untuk pertanyaan Anda:

"... Menyimpan Perubahan ( untuk setiap catatan ) ..."

Itu hal terburuk yang bisa Anda lakukan! Memanggil SaveChanges()untuk setiap record memperlambat insert massal sangat turun Saya akan melakukan beberapa tes sederhana yang kemungkinan besar akan meningkatkan kinerja:

  • Panggil SaveChanges()sekali setelah SEMUA catatan.
  • Panggil SaveChanges()setelah misalnya 100 catatan.
  • Panggil SaveChanges()setelah misalnya 100 catatan dan buang konteks dan buat yang baru.
  • Nonaktifkan deteksi perubahan

Untuk sisipan massal, saya bekerja dan bereksperimen dengan pola seperti ini:

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

Saya memiliki program pengujian yang memasukkan 560.000 entitas (9 properti skalar, tanpa properti navigasi) ke dalam DB. Dengan kode ini berfungsi kurang dari 3 menit.

Untuk kinerja, penting untuk memanggil SaveChanges()setelah "banyak" catatan ("banyak" sekitar 100 atau 1000). Ini juga meningkatkan kinerja untuk membuang konteks setelah SaveChanges dan membuat yang baru. Ini membersihkan konteks dari semua entites, SaveChangestidak melakukan itu, entitas masih melekat pada konteks di negara Unchanged. Ini adalah ukuran pertumbuhan entitas terlampir dalam konteks yang memperlambat penyisipan langkah demi langkah. Jadi, sangat membantu untuk menghapusnya setelah beberapa waktu.

Berikut adalah beberapa pengukuran untuk entitas 560000 saya:

  • commitCount = 1, createateContext = false: berjam-jam (Itulah prosedur Anda saat ini)
  • commitCount = 100, createateContext = false: lebih dari 20 menit
  • commitCount = 1000, createateContext = false: 242 dtk
  • commitCount = 10000, createateContext = false: 202 dtk
  • commitCount = 100000, createateContext = false: 199 dtk
  • commitCount = 1000000, reinateContext = false: kehabisan memori
  • commitCount = 1, createateContext = true: lebih dari 10 menit
  • commitCount = 10, createateContext = true: 241 dtk
  • commitCount = 100, createateContext = true: 164 dtk
  • commitCount = 1000, createateContext = true: 191 dtk

Perilaku dalam tes pertama di atas adalah bahwa kinerjanya sangat non-linear dan menurun sangat dari waktu ke waktu. ("Banyak jam" adalah perkiraan, saya tidak pernah menyelesaikan tes ini, saya berhenti di 50.000 entitas setelah 20 menit.) Perilaku non-linear ini tidak begitu signifikan dalam semua tes lainnya.


89
@ Bongo Sharp: Jangan lupa untuk mengatur AutoDetectChangesEnabled = false;pada DbContext. Ini juga memiliki efek kinerja tambahan yang besar: stackoverflow.com/questions/5943394/...
Slauma

6
Ya, masalahnya adalah saya menggunakan Entity Framework 4, dan AutoDetectChangesEnabled adalah bagian dari 4.1, namun demikian, saya melakukan tes kinerja dan saya memiliki HASIL LUAR BIASA, mulai dari 00:12:00 hingga 00:00:22 SavinChanges pada setiap entitas sedang melakukan olverload ... TERIMA KASIH untuk answare Anda! ini yang saya cari
Bongo Sharp

10
Terima kasih atas konteksnya. Konfigurasi. AutoDetectChangesEnabled = false; tip, itu membuat perbedaan besar .
douglaz

1
@ dahacker89: Apakah Anda menggunakan versi yang benar EF> = 4.1 dan DbContext, TIDAK ObjectContext?
Slauma

3
@ dahacker89: Saya sarankan Anda membuat pertanyaan terpisah untuk masalah Anda dengan detail lebih lanjut. Saya tidak dapat menemukan apa yang salah di sini.
Slauma

176

Kombinasi ini meningkatkan kecepatan dengan cukup baik.

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

46
Jangan menonaktifkan secara buta ValidateOnSaveEnabled Anda mungkin bergantung pada perilaku itu, dan tidak menyadarinya sampai terlambat. Sekali lagi Anda mungkin melakukan validasi di tempat lain dalam kode dan memiliki EF memvalidasi lagi sama sekali tidak perlu.
Jeremy Cook

1
Dalam pengujian saya, menghemat 20.000 baris turun dari 101 detik menjadi 88 detik. Tidak banyak dan apa implikasinya.
AH.

27
@JeremyCook Saya pikir apa yang Anda coba dapatkan adalah jawaban ini akan jauh lebih baik jika menjelaskan implikasi yang mungkin terjadi dari mengubah properti ini dari nilai default mereka (selain dari peningkatan kinerja). Saya setuju.
pseudocoder

1
Ini bekerja untuk saya, walaupun jika Anda memperbarui catatan dalam konteks, Anda perlu menelepon DetectChanges () secara eksplisit
hillstuk

2
Ini dapat dinonaktifkan dan kemudian diaktifkan kembali dengan blok coba-akhirnya: msdn.microsoft.com/en-us/data/jj556205.aspx
yellavon

98

Cara tercepat akan menggunakan ekstensi insert massal , yang saya kembangkan

Catatan: ini adalah produk komersial, tidak gratis

Ia menggunakan SqlBulkCopy dan datareader khusus untuk mendapatkan kinerja maksimal. Akibatnya lebih dari 20 kali lebih cepat daripada menggunakan insert biasa atau AddRange EntityFramework.BulkInsert vs EF AddRange

penggunaannya sangat sederhana

context.BulkInsert(hugeAmountOfEntities);

10
Cepat tetapi hanya lapisan atas hierarki.
CAD berbicara

66
Ini tidak gratis.
Amir Saniyan

72
Iklan semakin pintar ... ini adalah produk berbayar dan sangat mahal untuk freelance. Diperingatkan!
JulioQc

35
USD600 untuk dukungan dan peningkatan 1 tahun? Apakah Anda sudah gila?
Camilo Terevinto

7
Saya bukan lagi pemilik produk
maxlego

83

Anda harus melihat menggunakan System.Data.SqlClient.SqlBulkCopyini. Berikut dokumentasinya , dan tentu saja ada banyak tutorial online.

Maaf, saya tahu Anda sedang mencari jawaban sederhana untuk membuat EF melakukan apa yang Anda inginkan, tetapi operasi massal tidak benar-benar dimaksudkan untuk ORM.


1
Saya telah mengalami SqlBulkCopy beberapa kali saat meneliti ini, tetapi tampaknya lebih berorientasi pada sisipan table-to-table, sayangnya saya tidak mengharapkan solusi yang mudah, tetapi lebih kepada tips kinerja, seperti misalnya mengelola Keadaan koneksi secara manual, insted membiarkan EF melakukannya untuk Anda
Bongo Sharp

7
Saya telah menggunakan SqlBulkCopy untuk memasukkan sejumlah besar data langsung dari aplikasi saya. Pada dasarnya anda harus membuat DataTable, mengisinya, kemudian lulus yang ke bulkcopy. Ada beberapa gotcha saat Anda menyiapkan DataTable Anda (sebagian besar saya sudah lupa, sayangnya), tetapi seharusnya berfungsi dengan baik
Adam Rackis

2
Saya melakukan pembuktian konsep, dan sebagai promissed, itu bekerja sangat cepat, tetapi salah satu alasan mengapa saya menggunakan EF adalah karena penyisipan data relasional lebih mudah, misalnya jika saya memasukkan entitas yang sudah berisi data relasional , itu juga akan memasukkannya, apakah Anda pernah masuk ke skenario ini? Terima kasih!
Bongo Sharp

2
Sayangnya memasukkan objek web ke dalam DBMS sebenarnya bukan sesuatu yang akan dilakukan BulkCopy. Itulah manfaat dari ORM seperti EF, biayanya adalah itu tidak akan skala untuk melakukan ratusan grafik objek serupa secara efisien.
Adam Rackis

2
SqlBulkCopy jelas merupakan cara untuk pergi jika Anda membutuhkan kecepatan mentah atau jika Anda akan menjalankan kembali sisipan ini. Saya telah memasukkan beberapa juta catatan sebelumnya dan ini sangat cepat. Karena itu, kecuali jika Anda perlu menjalankan kembali sisipan ini, mungkin lebih mudah menggunakan EF saja.
Neil

49

Saya setuju dengan Adam Rackis. SqlBulkCopyadalah cara tercepat untuk mentransfer catatan massal dari satu sumber data ke yang lain. Saya menggunakan ini untuk menyalin catatan 20K dan butuh waktu kurang dari 3 detik. Lihat contoh di bawah ini.

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
Saya mencoba banyak solusi yang disediakan dalam posting ini dan SqlBulkCopy sejauh ini tercepat. EF murni membutuhkan waktu 15 menit, tetapi dengan campuran solusi dan SqlBulkCopy, saya dapat mencapai 1,5 menit! Ini dengan 2 juta catatan! Tanpa optimasi indeks DB.
Jonas

Daftar lebih mudah daripada DataTable. Ada AsDataReader()metode ekstensi, dijelaskan dalam jawaban ini: stackoverflow.com/a/36817205/1507899
RJB

Tapi ini hanya untuk Entity top yang bukan relasional
Zahid Mustafa

1
@ZahidMustafa: yeah. Itu melakukan BulkInsert, bukan Bulk-Analysis-And-Relation-Tracing-On-Object-Graphs .. jika Anda ingin membahas hubungan, Anda harus menganalisis dan menentukan urutan penyisipan dan kemudian menyisipkan level individu dan mungkin memperbarui beberapa kunci sebagai dibutuhkan, dan Anda akan mendapatkan solusi yang dirancang khusus cepat. Atau, Anda dapat mengandalkan EF untuk melakukan itu, tidak ada pekerjaan di sisi Anda, tetapi lebih lambat saat runtime.
quetzalcoatl

23

Saya akan merekomendasikan artikel ini tentang cara melakukan sisipan massal menggunakan EF.

Kerangka Entitas dan MASUK massal lambat

Dia mengeksplorasi bidang-bidang ini dan membandingkan kinerja:

  1. EF default (57 menit untuk menyelesaikan penambahan 30.000 catatan)
  2. Mengganti dengan ADO.NET Code (25 detik untuk 30.000 yang sama)
  3. Context Bloat- Menyimpan Grafik Konteks aktif kecil dengan menggunakan konteks baru untuk setiap Unit Kerja (30.000 sisipan yang sama membutuhkan 33 detik)
  4. Daftar Besar - Matikan AutoDetectChangesEnabled (membawa waktu ke sekitar 20 detik)
  5. Batching (hingga 16 detik)
  6. DbTable.AddRange () - (kinerja dalam kisaran 12)

21

karena tidak pernah disebutkan di sini saya ingin recomment EFCore.BulkExtensions di sini

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
Saya mendukung saran ini. Setelah mencoba banyak solusi homebrew, potong insert saya menjadi 1 detik dari lebih dari 50 detik. Dan, itu adalah lisensi MIT yang begitu mudah digabungkan.
SouthShoreAK

apakah ini tersedia untuk ef 6.x
Alok

ini hanya lebih berkinerja daripada menggunakan AddRange jika lebih dari 10 entitas
Jackal

5
10.000 sisipan berubah dari 9 menit menjadi 12 detik. Ini patut mendapat perhatian lebih!
callisto

2
Jika ada cara untuk mengubah jawaban yang diterima, ini harus menjadi jawaban yang diterima modern sekarang. Dan saya berharap tim EF memberikan ini di luar kotak.
Tanveer Badar

18

Saya telah menyelidiki jawaban Slauma (yang luar biasa, terima kasih untuk idenya), dan saya telah mengurangi ukuran bets sampai saya mencapai kecepatan optimal. Melihat hasil Slauma:

  • commitCount = 1, createateContext = true: lebih dari 10 menit
  • commitCount = 10, createateContext = true: 241 dtk
  • commitCount = 100, createateContext = true: 164 dtk
  • commitCount = 1000, createateContext = true: 191 dtk

Terlihat bahwa ada peningkatan kecepatan ketika bergerak dari 1 ke 10, dan dari 10 ke 100, tetapi dari 100 hingga 1000 kecepatan memasukkan jatuh lagi.

Jadi saya fokus pada apa yang terjadi ketika Anda mengurangi ukuran bets menjadi nilai di antara 10 dan 100, dan inilah hasil saya (saya menggunakan konten baris yang berbeda, jadi waktu saya nilainya berbeda):

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

Berdasarkan hasil saya, sebenarnya optimal adalah sekitar nilai 30 untuk ukuran batch. Ini kurang dari 10 dan 100. Masalahnya adalah, saya tidak tahu mengapa 30 optimal, saya juga tidak bisa menemukan penjelasan logis untuk itu.


2
Saya menemukan hal yang sama dengan Postrges dan SQL murni (itu tergantung pada SQL bukan pada EF) yang 30 adalah optimal.
Kamil Gareev

Pengalaman saya adalah bahwa optimal berbeda untuk kecepatan koneksi dan ukuran baris yang berbeda. Untuk koneksi cepat dan baris kecil optimal bisa mencapai> 200 baris.
Jing

18

Seperti yang orang lain katakan, SqlBulkCopy adalah cara untuk melakukannya jika Anda ingin kinerja insert yang sangat bagus.

Agak sulit untuk diimplementasikan tetapi ada perpustakaan yang dapat membantu Anda. Ada beberapa di luar sana tapi saya akan tanpa malu mencabut perpustakaan saya sendiri saat ini: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

Satu-satunya kode yang Anda perlukan adalah:

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

Jadi seberapa cepat itu? Sangat sulit untuk mengatakan karena itu tergantung pada banyak faktor, kinerja komputer, jaringan, ukuran objek dll. Tes kinerja yang saya buat menunjukkan 25k entitas dapat dimasukkan pada sekitar 10-an dengan cara standar di host lokal JIKA Anda mengoptimalkan konfigurasi EF seperti disebutkan dalam jawaban lain. Dengan EFUtilities yang membutuhkan waktu sekitar 300ms. Yang lebih menarik adalah bahwa saya telah menyelamatkan sekitar 3 juta entitas dalam waktu kurang dari 15 detik menggunakan metode ini, rata-rata sekitar 200k entitas per detik.

Satu masalah tentu saja jika Anda perlu memasukkan data yang dirilis. Ini dapat dilakukan secara efisien ke dalam sql server menggunakan metode di atas tetapi mengharuskan Anda untuk memiliki strategi pembuatan Id yang memungkinkan Anda menghasilkan id di kode aplikasi untuk induk sehingga Anda dapat mengatur kunci asing. Ini dapat dilakukan dengan menggunakan GUID atau sesuatu seperti pembuatan HiLo id.


Bekerja dengan baik. Sintaksnya sedikit verbose. Berpikir akan lebih baik jika EFBatchOperationmemiliki konstruktor yang Anda lewati DbContextuntuk daripada melewati setiap metode statis. Versi generik InsertAlldan UpdateAllyang secara otomatis menemukan koleksi, mirip dengan DbContext.Set<T>, akan bagus juga.
kjbartel

Hanya komentar singkat untuk mengucapkan terima kasih! Kode ini memungkinkan saya untuk menyimpan catatan 170k dalam 1,5 detik! Benar-benar pukulan metode lain yang saya coba keluar dari air.
Tom Glenn

@Mikael Salah satu masalah berkaitan dengan bidang identitas. Apakah Anda memiliki cara untuk mengaktifkan penyisipan identitas?
Joe Phillips

1
Berbeda dengan EntityFramework.BulkInsert, perpustakaan ini tetap gratis. +1
Rudey

14

Dispose()konteks membuat masalah jika entitas yang Anda Add()andalkan pada entitas yang dimuat sebelumnya (misalnya properti navigasi) dalam konteks

Saya menggunakan konsep serupa untuk menjaga konteks saya kecil untuk mencapai kinerja yang sama

Tetapi alih-alih Dispose()konteksnya dan menciptakan kembali, saya cukup melepaskan entitas yang sudahSaveChanges()

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

bungkus dengan try catch dan TrasactionScope()jika perlu, jangan perlihatkan di sini untuk menjaga kode tetap bersih


1
Itu memperlambat insert (AddRange) menggunakan Entity Framework 6.0. Memasukkan 20.000 baris naik dari sekitar 101 detik menjadi 118 detik.
AH.

1
@Stephen Ho: Saya juga berusaha menghindari membuang konteks saya. Saya bisa mengerti ini lebih lambat daripada menciptakan kembali konteks, tetapi saya ingin tahu apakah Anda menemukan ini lebih cepat daripada tidak menciptakan kembali konteks tetapi dengan set commitCount.
Pelajar

@Pelajar: Saya pikir itu lebih cepat daripada menciptakan kembali konteksnya. Tapi saya tidak begitu ingat sekarang karena saya beralih untuk menggunakan SqlBulkCopy pada akhirnya.
Stephen Ho

Saya akhirnya harus menggunakan teknik ini karena, untuk beberapa alasan aneh, ada beberapa pelacakan yang tersisa terjadi pada pass kedua melalui loop sementara, meskipun saya memiliki semuanya dibungkus dengan pernyataan menggunakan dan bahkan disebut Buang () pada DbContext . Ketika saya akan menambah konteks (pada laluan ke-2), himpunan himpunan konteks akan melonjak menjadi 6 alih-alih hanya satu. Item-item lain yang ditambahkan secara sewenang-wenang telah dimasukkan dalam pass pertama melalui loop while sehingga panggilan ke SaveChanges akan gagal pada pass kedua (karena alasan yang jelas).
Hallmanac

9

Saya tahu ini adalah pertanyaan yang sangat lama, tetapi seorang pria di sini mengatakan bahwa mengembangkan metode ekstensi untuk menggunakan penyisipan massal dengan EF, dan ketika saya memeriksa, saya menemukan bahwa biaya perpustakaan $ 599 hari ini (untuk satu pengembang). Mungkin masuk akal untuk seluruh perpustakaan, namun untuk memasukkan sebagian besar ini terlalu banyak.

Berikut adalah metode ekstensi yang sangat sederhana yang saya buat. Saya menggunakannya berpasangan dengan database terlebih dahulu (jangan diuji dengan kode terlebih dahulu, tapi saya pikir itu berfungsi sama). Ubah YourEntitiesdengan nama konteks Anda:

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

Anda dapat menggunakannya untuk koleksi apa pun yang mewarisi IEnumerable, seperti itu:

await context.BulkInsertAllAsync(items);

silakan lengkapi kode contoh Anda. di mana bulkCopy
Seabizkit

1
Sudah ada di sini:await bulkCopy.WriteToServerAsync(table);
Guilherme

Mungkin saya tidak jelas, dalam tulisan Anda, Anda menyarankan Anda membuat ekstensi ... yang saya maksud berarti bahwa tidak ada lib bagian ke-3 diperlukan, padahal sebenarnya dalam kedua metode menggunakan lib SqlBulkCopy. Ini sepenuhnya bergantung pada SqlBulkCopy, ketika mengapa saya bertanya dari mana bulkCopy berasal, itu adalah ekstensi lib yang Anda tulis lib ekstensi di atas. Akan lebih masuk akal untuk mengatakan di sini adalah bagaimana saya menggunakan lib SqlBulkCopy.
Seabizkit

harus menggunakan conn.OpenAsync dalam versi async
Robert

6

Coba gunakan a Prosedur Tersimpan yang akan mendapatkan XML dari data yang ingin Anda masukkan.


9
Melewati data sebagai XML tidak diperlukan jika Anda tidak ingin menyimpannya sebagai XML. Di SQL 2008 Anda dapat menggunakan parameter tabel bernilai.
Ladislav Mrnka

saya tidak mengklarifikasi ini tetapi saya perlu juga mendukung SQL 2005
Bongo Sharp

4

Saya telah membuat ekstensi generik dari contoh @Slauma di atas;

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

Pemakaian:

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

Saya mencari cara tercepat untuk memasukkan ke dalam Kerangka Entitas

Ada beberapa perpustakaan pihak ketiga yang mendukung Sisipan Massal yang tersedia:

  • Z.EntityFramework.Extensions ( Disarankan )
  • EFUtilities
  • EntityFramework.BulkInsert

Lihat: Entity Framework Bulk Insert library

Hati-hati, saat memilih perpustakaan sisipan massal. Hanya Entity Framework Extensions yang mendukung semua jenis asosiasi dan warisan dan hanya itu yang masih didukung.


Penafian : Saya adalah pemilik Entity Framework Extensions

Perpustakaan ini memungkinkan Anda untuk melakukan semua operasi massal yang Anda butuhkan untuk skenario Anda:

  • Perubahan Simpan Massal
  • Sisipkan Massal
  • Hapus Massal
  • Pembaruan Massal
  • Gabung Massal

Contoh

// 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
ini adalah ekstensi yang bagus tetapi tidak gratis .
Okan Kocyigit

2
Jawaban ini cukup bagus dan EntityFramework.BulkInsert melakukan penyisipan massal baris 15K dalam 1,5 detik, berfungsi cukup baik untuk proses internal seperti Layanan Windows.
Pastor Cortes

4
Ya, $ 600 untuk memasukkan massal. Totaly sepadan.
eocron

1
@eocron Yeat, sangat berharga jika Anda menggunakannya secara komersial. Saya tidak melihat ada masalah dengan $ 600 untuk sesuatu yang saya tidak perlu menghabiskan berjam-jam membangunnya sendiri yang akan menelan biaya lebih dari $ 600. Ya itu membutuhkan uang tetapi melihat tingkat per jam saya itu menghabiskan uang dengan baik!
Jordy van Eijk

3

Gunakan 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

Salah satu cara tercepat untuk menyimpan daftar Anda harus menerapkan kode berikut

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

AutoDetectChangesEnabled = false

Tambah, AddRange & SaveChanges: Tidak mendeteksi perubahan.

ValidateOnSaveEnabled = false;

Tidak mendeteksi pelacak perubahan

Anda harus menambahkan nuget

Install-Package Z.EntityFramework.Extensions

Sekarang Anda dapat menggunakan kode berikut

var context = new MyContext();

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

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

dapatkah saya menggunakan Kode sampel Anda Untuk Pembaruan Massal?
AminGolmahalle

4
Perpustakaan Z tidak gratis
SHADOW.NET

3

SqlBulkCopy super cepat

Ini implementasi saya:

// 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

[Pembaruan 2019] EF Core 3.1

Mengikuti apa yang telah dikatakan di atas, menonaktifkan AutoDetectChangesEnabled di EF Core bekerja dengan sempurna: waktu penyisipan dibagi dengan 100 (dari beberapa menit hingga beberapa detik, catatan 10k dengan hubungan lintas tabel)

Kode yang diperbarui adalah:

  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

Berikut ini adalah perbandingan kinerja antara menggunakan Entity Framework dan menggunakan kelas SqlBulkCopy pada contoh realistis: Bagaimana Cara Massal Memasukkan Objek Kompleks ke dalam Database SQL Server

Seperti yang sudah ditekankan oleh orang lain, ORM tidak dimaksudkan untuk digunakan dalam operasi massal. Mereka menawarkan fleksibilitas, pemisahan kekhawatiran dan manfaat lainnya, tetapi operasi massal (kecuali pembacaan massal) bukan salah satunya.


2

Pilihan lain adalah menggunakan SqlBulkTools yang tersedia dari Nuget. Ini sangat mudah digunakan dan memiliki beberapa fitur canggih.

Contoh:

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

Lihat dokumentasi untuk contoh lebih lanjut dan penggunaan lanjutan. Penafian: Saya adalah penulis perpustakaan ini dan pandangan saya adalah pendapat saya sendiri.


2
Proyek ini telah dihapus dari NuGet dan GitHub.
0x

1

Sesuai pengetahuan saya ada no BulkInsertdiEntityFramework untuk meningkatkan kinerja sisipan besar.

Dalam skenario ini Anda dapat pergi dengan SqlBulkCopy di ADO.netuntuk memecahkan masalah Anda


Saya telah melihat kelas itu, tetapi tampaknya lebih berorientasi pada penyisipan table-to-table, bukan?
Bongo Sharp

Tidak yakin apa yang Anda maksud, ada kelebihan beban WriteToServeryang membutuhkan DataTable.
Blindy

tidak, Anda dapat menyisipkan dari. Net objek ke SQL juga. Apa yang Anda cari?
anishMarokey

Cara untuk memasukkan ribuan rekaman yang berpotensi ke dalam basis data di dalam blok TransactionScope
Bongo Sharp

Anda dapat menggunakan Net TransactionScope technet.microsoft.com/en-us/library/bb896149.aspx
anishMarokey

1

Pernahkah Anda mencoba memasukkan melalui pekerja latar belakang atau tugas?

Dalam kasus saya, saya memasukkan register 7760, didistribusikan di 182 tabel berbeda dengan hubungan kunci asing (oleh NavigationProperties).

Tanpa tugas, butuh 2 menit setengah. Dalam Tugas (Task.Factory.StartNew(...) ), butuh 15 detik.

Saya hanya melakukan SaveChanges()setelah menambahkan semua entitas ke konteks. (untuk memastikan integritas data)


2
Saya cukup yakin bahwa konteksnya tidak aman untuk thread. Apakah Anda memiliki tes untuk memastikan bahwa semua entitas telah disimpan?
Danny Varod

Saya tahu kerangka seluruh entitas bukan thread aman sama sekali, tetapi saya hanya menambahkan objek ke konteks dan menyimpan pada akhirnya ... Ini bekerja dengan sempurna di sini.
Rafael AMS

Jadi, Anda memanggil DbContext.SaveChanges () di utas utama, tetapi menambahkan entitas ke konteks dilakukan di utas latar belakang, bukan?
Prokurors

1
Ya, tambahkan data di dalam utas; tunggu semua selesai; dan Simpan Perubahan di utas utama
Rafael AMS

Meskipun saya pikir cara ini berbahaya dan rentan terhadap kesalahan, saya merasa sangat menarik.
Pelajar

1

Semua solusi yang ditulis di sini tidak membantu karena ketika Anda melakukan SaveChanges (), masukkan pernyataan dikirim ke database satu per satu, itulah cara Entity bekerja.

Dan jika perjalanan Anda ke database dan kembali adalah 50 ms misalnya maka waktu yang diperlukan untuk memasukkan adalah jumlah catatan x 50 ms.

Anda harus menggunakan BulkInsert, ini tautannya: https://efbulkinsert.codeplex.com/

Saya mendapat waktu memasukkan berkurang dari 5-6 menit menjadi 10-12 detik dengan menggunakannya.



1

[SOLUSI BARU UNTUK POSTGRESQL] Hei, saya tahu ini posting yang cukup lama, tapi saya baru saja mengalami masalah yang sama, tetapi kami menggunakan Postgresql. Saya ingin menggunakan bulkinsert yang efektif, yang ternyata cukup sulit. Saya belum menemukan perpustakaan gratis yang tepat untuk melakukannya pada DB ini. Saya hanya menemukan pembantu ini: https://bytefish.de/blog/postgresql_bulk_insert/ yang juga ada di Nuget. Saya telah menulis mapper kecil, yang secara otomatis memetakan properti dengan cara Entity Framework:

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

Saya menggunakannya dengan cara berikut (Saya memiliki entitas bernama Undertaking):

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

Saya menunjukkan contoh dengan transaksi, tetapi juga bisa dilakukan dengan koneksi normal yang diambil dari konteks. undertakingsToAdd adalah enumerable dari catatan entitas normal, yang ingin saya masukkan ke DB.

Solusi ini, yang saya dapatkan setelah beberapa jam meneliti dan mencoba, adalah seperti yang Anda harapkan jauh lebih cepat dan akhirnya mudah digunakan dan gratis! Saya benar-benar menyarankan Anda untuk menggunakan solusi ini, tidak hanya karena alasan yang disebutkan di atas, tetapi juga karena itu satu-satunya yang saya tidak punya masalah dengan Postgresql itu sendiri, banyak solusi lain bekerja dengan sempurna misalnya dengan SqlServer.


0

Rahasianya adalah memasukkan ke dalam tabel pementasan kosong yang identik. Sisipan cepat keringanan. Kemudian jalankan satu insert dari itu ke dalam tabel besar utama Anda. Kemudian potong tabel pementasan siap untuk batch berikutnya.

yaitu.

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

Dengan menggunakan EF, tambahkan semua catatan Anda ke tabel pementasan yang kosong. Kemudian gunakan SQL untuk memasukkan ke dalam tabel utama (besar dan lambat) dalam satu instruksi SQL. Kemudian kosongkan meja pementasan Anda. Ini adalah cara yang sangat cepat untuk memasukkan banyak data ke dalam tabel yang sudah besar.
Simon Hughes

13
Ketika Anda mengatakan menggunakan EF, tambahkan catatan ke tabel pementasan, apakah Anda benar-benar mencoba ini dengan EF? Karena EF mengeluarkan panggilan terpisah ke database dengan setiap sisipan, saya curiga Anda akan melihat hit perf yang sama yang OP coba hindari. Bagaimana tabel pementasan menghindari masalah ini?
Jim Wooley

-1

Tapi, untuk lebih dari (+4000) sisipan saya sarankan untuk menggunakan prosedur tersimpan. terlampir waktu berlalu. Saya memang memasukkannya 11.788 baris dalam 20 "masukkan deskripsi gambar di sini

itu kode itu

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

-1

Gunakan prosedur tersimpan yang mengambil input data dalam bentuk xml untuk memasukkan data.

Dari kode c # Anda, masukkan data yang dimasukkan sebagai xml.

misal dalam c #, sintaksnya akan seperti ini:

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

-7

Gunakan teknik ini untuk meningkatkan kecepatan memasukkan catatan dalam Entity Framework. Di sini saya menggunakan prosedur tersimpan sederhana untuk memasukkan catatan. Dan untuk menjalankan prosedur tersimpan ini saya menggunakan metode .FromSql () dari Entity Framework yang mengeksekusi Raw SQL.

Kode prosedur yang tersimpan:

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

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

Selanjutnya, lewati semua 4000 record Anda dan tambahkan kode Entity Framework yang mengeksekusi yang tersimpan

onces prosedur setiap loop ke-100.

Untuk ini saya membuat kueri string untuk menjalankan prosedur ini, terus menambahkannya setiap set catatan.

Kemudian periksa apakah loop berjalan dalam kelipatan 100 dan dalam hal itu jalankan menggunakan .FromSql().

Jadi untuk 4000 rekaman saya hanya perlu menjalankan prosedur hanya 4000/100 = 40 kali .

Periksa kode di bawah ini:

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

Ini mungkin efisien tetapi setara dengan TIDAK menggunakan kerangka kerja entitas. Pertanyaan OP adalah bagaimana memaksimalkan efisiensi dalam konteks Entity Framework
kall2sollies
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.