วิธีแทรกที่เร็วที่สุดใน Entity Framework


682

ฉันกำลังมองหาวิธีที่เร็วที่สุดในการแทรกลงใน Entity Framework

ฉันถามสิ่งนี้เพราะสถานการณ์ที่คุณมี TransactionScope ที่ใช้งานอยู่และการแทรกมีขนาดใหญ่มาก (4000+) อาจใช้เวลานานกว่า 10 นาที (การหมดเวลาเริ่มต้นของการทำธุรกรรม) และสิ่งนี้จะนำไปสู่การทำธุรกรรมที่ไม่สมบูรณ์


1
ปัจจุบันคุณเป็นอย่างไรบ้าง
ดัสติน Laine

การสร้าง TransactionScope, การสร้างอินสแตนซ์ DBContext, การเปิดการเชื่อมต่อ, และสำหรับแต่ละคำสั่งที่ทำการแทรกและ SavingChanges (สำหรับแต่ละเรคคอร์ด), หมายเหตุ: TransactionScope และ DBContext กำลังใช้คำสั่งและฉันกำลังปิดการเชื่อมต่อในที่สุด block
Bongo Sharp

อีกคำตอบสำหรับการอ้างอิง: stackoverflow.com/questions/5798646/…
Ladislav Mrnka

2
วิธีที่เร็วที่สุดในการแทรกลงในฐานข้อมูล SQLไม่เกี่ยวข้องกับ EF AFAIK BCP จากนั้นเป็น TVP + ผสาน / แทรก
StingyJack

1
สำหรับผู้ที่จะอ่านความคิดเห็น: ส่วนใหญ่คำตอบที่ทันสมัยอยู่ที่นี่
Tanveer Badar

คำตอบ:


986

ถึงคำพูดของคุณในความคิดเห็นสำหรับคำถามของคุณ:

"... SavingChanges ( สำหรับแต่ละระเบียน ) ... "

นั่นคือสิ่งที่แย่ที่สุดที่คุณสามารถทำได้! การเรียกSaveChanges()แต่ละระเบียนทำให้เม็ดมีดช้าลงอย่างมาก ฉันจะทำแบบทดสอบง่ายๆสองสามข้อซึ่งมีแนวโน้มว่าจะปรับปรุงประสิทธิภาพ:

  • โทรSaveChanges()หนึ่งครั้งหลังจากบันทึกทั้งหมด
  • โทรSaveChanges()ไปเพื่อขอตัวอย่าง 100 เรคคอร์ด
  • โทร 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 นาที

เพื่อประสิทธิภาพการทำงานเป็นสิ่งสำคัญที่จะเรียกใช้SaveChanges()หลังจากบันทึก "หลาย" ("หลาย" ประมาณ 100 หรือ 1,000) นอกจากนี้ยังปรับปรุงประสิทธิภาพการทำงานเพื่อกำจัดบริบทหลังจาก SaveChanges และสร้างใหม่ สิ่งนี้จะล้างบริบทจากการเข้าร่วมทั้งหมดSaveChangesไม่ได้ทำสิ่งนั้นสิ่งที่แนบมายังคงอยู่กับบริบทที่อยู่ในสถานะUnchangedไม่ได้ทำกิจการที่ยังคงติดอยู่กับบริบทในรัฐมันคือขนาดที่เพิ่มขึ้นของเอนทิตีที่แนบมาในบริบทที่ช้าลงทีละขั้นตอนการแทรก ดังนั้นจึงเป็นประโยชน์ในการล้างหลังจากเวลา

นี่เป็นวัดไม่กี่ 560000 หน่วยงานของฉันคือ

  • commitCount = 1, recreateContext = false: หลายชั่วโมง (นั่นคือขั้นตอนปัจจุบันของคุณ)
  • commitCount = 100, recreateContext = false: มากกว่า 20 นาที
  • commitCount = 1000, recreateContext = false: 242 วินาที
  • commitCount = 10000, recreateContext = false: 202 วินาที
  • commitCount = 100000, recreateContext = false: 199 วินาที
  • commitCount = 1000000, recreateContext = false: ออกจากข้อยกเว้นหน่วยความจำ
  • commitCount = 1, recreateContext = true: มากกว่า 10 นาที
  • commitCount = 10, recreateContext = true: 241 วินาที
  • commitCount = 100, recreateContext = true: 164 วินาที
  • commitCount = 1,000, recreateContext = true: 191 วินาที

พฤติกรรมในการทดสอบครั้งแรกข้างต้นคือประสิทธิภาพไม่เชิงเส้นและลดลงอย่างมากเมื่อเวลาผ่านไป ("หลายชั่วโมง" เป็นการประเมินฉันไม่เคยทำแบบทดสอบนี้ฉันหยุดที่ 50,000 หน่วยงานหลังจาก 20 นาที) พฤติกรรมที่ไม่ใช่เชิงเส้นนี้ไม่ได้มีความสำคัญในการทดสอบอื่น ๆ ทั้งหมด


89
@Bongo Sharp: อย่าลืมตั้งค่าAutoDetectChangesEnabled = false;ใน DbContext นอกจากนี้ยังมีเอฟเฟกต์เพิ่มเติมที่มีประสิทธิภาพยิ่งใหญ่: stackoverflow.com/questions/5943394/…
Slauma

6
ใช่ปัญหาคือฉันใช้ Entity Framework 4 และ AutoDetectChangesEnabled เป็นส่วนหนึ่งของ 4.1 อย่างไรก็ตามฉันทำการทดสอบประสิทธิภาพและฉันมีผลลัพธ์ที่น่าอัศจรรย์มันเปลี่ยนจาก 00:12:00 ถึง 00:00:22 SavinChanges ในแต่ละกิจการกำลังทำ olverload ... ขอบคุณมากสำหรับการตอบรับของคุณ! นี่คือสิ่งที่ฉันกำลังมองหา
Bongo Sharp

10
ขอบคุณสำหรับบริบทการกำหนดค่า. AutoDetectChangesEnabled = false; ทิปมันสร้างความแตกต่างอย่างมาก
douglaz

1
@ dahacker89: คุณใช้รุ่นที่ถูกต้อง EF> = 4.1 และDbContextไม่ใช่ObjectContext?
Slauma

3
@ dahacker89: ฉันขอแนะนำให้คุณสร้างคำถามแยกต่างหากสำหรับปัญหาของคุณโดยอาจมีรายละเอียดเพิ่มเติม ฉันไม่สามารถเข้าใจได้ว่าเกิดอะไรขึ้นที่นี่
Slauma

176

ชุดนี้เพิ่มความเร็วได้ดีพอ

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

46
อย่าปิดการใช้งาน ValidateOnSaveEnabled อย่างเด็ดขาดคุณอาจขึ้นอยู่กับพฤติกรรมนั้นและไม่รู้ตัวจนกว่าจะสายเกินไป จากนั้นอีกครั้งคุณอาจทำการตรวจสอบความถูกต้องที่อื่นในรหัสและการตรวจสอบความถูกต้องของ EF อีกครั้งนั้นไม่จำเป็นอย่างสมบูรณ์
Jeremy Cook

1
ในการทดสอบของฉันการประหยัด 20,000 แถวลดลงจาก 101 วินาทีเป็น 88 วินาที ไม่มากและสิ่งที่เกี่ยวข้อง
อา

27
@ JeremyCook ฉันคิดว่าสิ่งที่คุณพยายามทำคือคำตอบนี้จะดีกว่ามากถ้ามันอธิบายความหมายที่เป็นไปได้ของการเปลี่ยนคุณสมบัติเหล่านี้จากค่าเริ่มต้น (นอกเหนือจากการปรับปรุงประสิทธิภาพ) ฉันเห็นด้วย.
pseudocoder

1
สิ่งนี้ใช้งานได้สำหรับฉันแม้ว่าคุณจะอัปเดตบันทึกในบริบทคุณจะต้องโทรหา DetectChanges () อย่างชัดเจน
hillstuk

2
สิ่งเหล่านี้สามารถปิดการใช้งานและเปิดใช้งานใหม่ด้วยการลองบล็อกในที่สุด: msdn.microsoft.com/en-us/data/jj556205.aspx
yellavon

98

วิธีที่เร็วที่สุดคือใช้ส่วนขยายการแทรกจำนวนมากซึ่งฉันได้พัฒนาขึ้น

หมายเหตุ: นี่เป็นผลิตภัณฑ์เชิงพาณิชย์ไม่เสียค่าใช้จ่าย

มันใช้ SqlBulkCopy และตัวเก็บข้อมูลที่กำหนดเองเพื่อรับประสิทธิภาพสูงสุด เป็นผลให้เร็วกว่าการใช้การแทรกปกติหรือ AddRange มากกว่า 20 เท่า EntityFramework.BulkInsert กับ EF AddRange

การใช้งานง่ายมาก

context.BulkInsert(hugeAmountOfEntities);

10
รวดเร็ว แต่ทำเฉพาะเลเยอร์บนสุดของลำดับชั้นเท่านั้น
คนเจ้าเล่ห์ CAD

66
มันไม่ฟรี
Amir Saniyan

72
โฆษณากำลังได้รับอย่างชาญฉลาด ... นี่เป็นผลิตภัณฑ์ที่ต้องชำระเงินและมีราคาแพงมากสำหรับการทำงานอิสระ ถูกเตือน!
JulioQc

35
USD600 สำหรับการสนับสนุนและอัปเกรด 1 ปี คุณไม่อยู่ในใจของคุณ?
Camilo Terevinto

7
ฉันไม่ได้เป็นเจ้าของผลิตภัณฑ์อีกต่อไป
maxlego

83

คุณควรดูที่การใช้สิ่งSystem.Data.SqlClient.SqlBulkCopyนี้ นี่คือเอกสารประกอบและแน่นอนว่ามีบทเรียนออนไลน์มากมาย

ขออภัยฉันรู้ว่าคุณกำลังมองหาคำตอบง่ายๆเพื่อให้ EF ทำสิ่งที่คุณต้องการ แต่การดำเนินการแบบกลุ่มไม่ใช่สิ่งที่ ORM หมายถึง


1
ฉันได้พบกับ SqlBulkCopy สองสามครั้งในขณะที่ค้นคว้าสิ่งนี้ แต่ดูเหมือนว่าจะเน้นไปที่การแทรกแบบ Table-to-Table มากขึ้นน่าเศร้าที่ฉันไม่ได้คาดหวังวิธีแก้ปัญหาง่าย ๆ แต่เป็นเคล็ดลับประสิทธิภาพเช่นการจัดการสถานะของ การเชื่อมต่อด้วยตนเองไม่ต้องให้ EF ทำเพื่อคุณ
Bongo Sharp

7
ฉันใช้ SqlBulkCopy เพื่อแทรกข้อมูลจำนวนมากจากแอปพลิเคชันของฉัน โดยทั่วไปคุณจะต้องสร้าง DataTable เติมข้อมูลแล้วส่งผ่านไปที่ BulkCopy มี gotchas สองสามตัวที่คุณตั้งค่า DataTable ของคุณ (ซึ่งส่วนใหญ่ฉันลืมไปแล้วน่าเศร้า) แต่มันก็ใช้ได้ดี
Adam Rackis

2
ฉันทำข้อพิสูจน์แนวคิดและเป็นสัญญามันทำงานได้อย่างรวดเร็วจริงๆ แต่หนึ่งในเหตุผลที่ฉันใช้ EF เพราะการแทรกข้อมูลเชิงสัมพันธ์นั้นง่ายกว่าเช่นถ้าฉันใส่เอนทิตีที่มีข้อมูลเชิงสัมพันธ์อยู่แล้ว มันจะแทรกเข้าไปด้วยคุณเคยมีเหตุการณ์นี้ไหม? ขอบคุณ!
Bongo Sharp

2
น่าเสียดายที่การใส่เว็บของวัตถุใน DBMS ไม่ใช่สิ่งที่ BulkCopy จะทำ นั่นคือประโยชน์ของ ORM เช่น EF ค่าใช้จ่ายที่จะไม่ขยายเพื่อทำกราฟวัตถุที่คล้ายกันหลายร้อยรายการอย่างมีประสิทธิภาพ
Adam Rackis

2
SqlBulkCopy เป็นหนทางที่แน่นอนที่จะไปหากคุณต้องการความเร็วแบบดิบหรือหากคุณจะเรียกใช้ส่วนแทรกนี้อีกครั้ง ฉันได้แทรกหลายล้านบันทึกไว้ก่อนหน้านี้และมันเร็วมาก ที่กล่าวไว้เว้นแต่คุณจะต้องเรียกใช้ส่วนแทรกนี้อีกครั้งมันอาจจะง่ายกว่าที่จะใช้ EF
Neil

49

ฉันเห็นด้วยกับ Adam Rackis 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();
        }

    }
}

1
ฉันลองใช้วิธีแก้ปัญหามากมายที่ให้ไว้ในโพสต์นี้และ SqlBulkCopy เร็วที่สุด Pure EF ใช้เวลา 15 นาที แต่ด้วยวิธีการผสมผสานและ SqlBulkCopy ฉันสามารถลงไปได้ 1.5 นาที! นี่คือบันทึก 2 ล้าน! ไม่มีการเพิ่มประสิทธิภาพดัชนี DB ใด ๆ
jonas

รายการง่ายกว่า DataTable มีAsDataReader()วิธีการขยายที่อธิบายไว้ในคำตอบนี้: stackoverflow.com/a/36817205/1507899
RJB

แต่มันสำหรับเอนทิตี้ชั้นนำเท่านั้นที่ไม่เกี่ยวกับความสัมพันธ์
Zahid Mustafa

1
@ZahidMustafa: ใช่ กำลังดำเนินการ BulkInsert ไม่ใช่การวิเคราะห์ - และ - ความสัมพันธ์ - การติดตาม - บน - วัตถุ - กราฟจำนวนมาก .. หากคุณต้องการครอบคลุมความสัมพันธ์คุณต้องวิเคราะห์และกำหนดลำดับการแทรกจากนั้นแทรกระดับแต่ละระดับและอาจอัปเดตบางคีย์เป็น จำเป็นและคุณจะได้รับโซลูชันที่ปรับแต่งตามความต้องการได้อย่างรวดเร็ว หรือคุณสามารถพึ่งพา EF ในการทำเช่นนั้นไม่ได้ผลข้างเคียงของคุณ แต่ช้าลงขณะรันไทม์
quetzalcoatl

23

ฉันอยากจะแนะนำบทความนี้เกี่ยวกับวิธีการแทรกจำนวนมากโดยใช้ EF

Entity Framework และ INSERT จำนวนมากที่ช้า

เขาสำรวจพื้นที่เหล่านี้และเปรียบเทียบประสิทธิภาพ:

  1. ค่าเริ่มต้นของ EF (57 นาทีเพื่อเพิ่ม 30,000 เรคคอร์ดให้สมบูรณ์)
  2. แทนที่ด้วยรหัส ADO.NET (25 วินาทีสำหรับผู้ที่เหมือนกัน 30,000 คน)
  3. Bloat บริบท - ทำให้กราฟบริบทที่ใช้งานอยู่มีขนาดเล็กโดยใช้บริบทใหม่สำหรับแต่ละหน่วยของงาน (แทรก 30,000 เดียวกันใช้เวลา 33 วินาที)
  4. รายการใหญ่ - ปิด AutoDetectChangesEnabled (ทำให้เวลาลดลงเหลือประมาณ 20 วินาที)
  5. การผสม (ลงไป 16 วินาที)
  6. DbTable.AddRange () - (ประสิทธิภาพอยู่ในช่วง 12)

21

ตามที่มันไม่เคยพูดถึงที่นี่ฉันต้องการที่จะ recomment EFCore.Bulk ส่วนขยายที่นี่

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
ฉันสองข้อเสนอแนะนี้ หลังจากลองใช้วิธีแก้ไขปัญหา homebrew จำนวนมากสิ่งนี้จะลดการแทรกของฉันลงเหลือ 1 วินาทีจากมากกว่า 50 วินาที และมันเป็นลิขสิทธิ์ของ MIT ที่รวมเข้าด้วยกันได้ง่าย
SouthShoreAK

สิ่งนี้มีประโยชน์สำหรับ ef 6.x
Alok

นี่เป็นเพียงประสิทธิภาพมากกว่าการใช้ AddRange ถ้ามันมีมากกว่า 10 เอนทิตี
Jackal

5
เม็ดมีด 10,000 เม็ดเปลี่ยนจาก 9 นาทีเป็น 12 วินาที สิ่งนี้สมควรได้รับความสนใจมากขึ้น!
callisto

2
หากมีวิธีใดที่จะเปลี่ยนคำตอบที่ยอมรับได้นี่ควรเป็นคำตอบที่ได้รับการยอมรับในปัจจุบัน และฉันหวังว่าทีม EF จะให้สิ่งนี้ออกนอกกรอบ
Tanveer Badar

18

ฉันได้ตรวจสอบคำตอบของ Slauma (ซึ่งยอดเยี่ยมมากขอบคุณสำหรับนักคิด) และฉันได้ลดขนาดแบทช์จนกว่าจะถึงความเร็วสูงสุด ดูผลลัพธ์ของ Slauma:

  • commitCount = 1, recreateContext = true: มากกว่า 10 นาที
  • commitCount = 10, recreateContext = true: 241 วินาที
  • commitCount = 100, recreateContext = true: 164 วินาที
  • commitCount = 1,000, recreateContext = true: 191 วินาที

จะเห็นได้ว่ามีความเร็วเพิ่มขึ้นเมื่อเคลื่อนที่จาก 1 เป็น 10 และจาก 10 เป็น 100 แต่จากความเร็วการแทรก 100 ถึง 1,000 จะลดลงอีกครั้ง

ดังนั้นฉันจึงมุ่งเน้นไปที่สิ่งที่เกิดขึ้นเมื่อคุณลดขนาดแบทช์ให้มีค่าระหว่าง 10 ถึง 100 และนี่คือผลลัพธ์ของฉัน (ฉันใช้เนื้อหาแถวที่แตกต่างกันดังนั้นเวลาของฉันจึงมีค่าแตกต่างกัน):

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

จากผลลัพธ์ของฉันค่าจริงที่เหมาะสมคือประมาณ 30 สำหรับขนาดแบทช์ มันน้อยกว่าทั้ง 10 และ 100 ปัญหาคือฉันไม่รู้ว่าทำไมถึง 30 ที่ดีที่สุดและฉันไม่สามารถหาคำอธิบายเชิงตรรกะได้


2
ฉันพบเหมือนกันกับ Postrges และ SQL บริสุทธิ์ (มันขึ้นอยู่กับ SQL ไม่ใช่ใน EF) ว่า 30 นั้นดีที่สุด
Kamil Gareev

ประสบการณ์ของฉันนั้นเหมาะสมที่สุดแตกต่างกันสำหรับความเร็วการเชื่อมต่อและขนาดของแถวที่แตกต่างกัน สำหรับการเชื่อมต่อที่รวดเร็วและแถวขนาดเล็กอาจมีค่ามากกว่า 200 แถว
jing

18

อย่างที่คนอื่น ๆ บอกว่า SqlBulkCopy เป็นวิธีที่จะทำถ้าคุณต้องการประสิทธิภาพการแทรกที่ดีจริงๆ

มันค่อนข้างยุ่งยากในการติดตั้ง แต่มีห้องสมุดที่สามารถช่วยเหลือคุณได้ มีอยู่ไม่กี่แห่งที่นั่น แต่ฉันจะลงคอห้องสมุดของตัวเองในเวลานี้: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

รหัสเดียวที่คุณต้องการคือ:

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

แล้วมันเร็วเท่าไหร่? ยากมากที่จะพูดเพราะมันขึ้นอยู่กับปัจจัยหลายอย่างประสิทธิภาพของคอมพิวเตอร์เครือข่ายขนาดของวัตถุ ฯลฯ การทดสอบประสิทธิภาพที่ฉันทำแนะนำให้ใส่เอนทิตี 25k ในเวลาประมาณ 10 วินาทีด้วยวิธีมาตรฐานบน localhost ถ้าคุณปรับการตั้งค่า EF ให้เหมาะสม กล่าวถึงในคำตอบอื่น ๆ ด้วย EFUtilities ที่ใช้เวลาประมาณ 300ms สิ่งที่น่าสนใจยิ่งกว่าคือฉันได้บันทึกหน่วยงานต่างๆกว่า 3 ล้านหน่วยในเวลาไม่ถึง 15 วินาทีโดยใช้วิธีนี้โดยเฉลี่ยประมาณ 200 หน่วยงานต่อวินาที

ปัญหาหนึ่งคือ ofcourse ถ้าคุณต้องการแทรกข้อมูลที่สัมพันธ์กัน สิ่งนี้สามารถทำได้อย่างมีประสิทธิภาพในเซิร์ฟเวอร์ sql โดยใช้วิธีการด้านบน แต่คุณต้องมีกลยุทธ์การสร้างรหัสที่ให้คุณสร้างรหัสในรหัสแอปสำหรับผู้ปกครองเพื่อให้คุณสามารถตั้งค่าคีย์ต่างประเทศ สิ่งนี้สามารถทำได้โดยใช้ GUID หรือบางอย่างเช่นการสร้างรหัส HiLo


ทำได้ดี. ไวยากรณ์เป็น verbose เล็กน้อย คิดว่ามันจะดีกว่าถ้าEFBatchOperationมีคอนสตรัคเตอร์ที่คุณส่งผ่านDbContextไปยังแทนที่จะส่งผ่านไปยังทุกวิธีคงที่ รุ่นทั่วไปInsertAllและUpdateAllที่ค้นหาคอลเลกชันโดยอัตโนมัติคล้ายกับDbContext.Set<T>จะดีเช่นกัน
kjbartel

เป็นเพียงความคิดเห็นด่วนที่จะกล่าวขอบคุณ! รหัสนี้อนุญาตให้ฉันบันทึก 170k ระเบียนใน 1.5 วินาที! เป่าวิธีอื่นโดยสิ้นเชิงที่ฉันได้ลองจากน้ำ
Tom Glenn

@Mikael ปัญหาหนึ่งคือการจัดการกับเขตข้อมูลตัวตน คุณมีวิธีเปิดใช้งานการแทรกข้อมูลประจำตัวหรือยัง
Joe Phillips

1
ตรงกันข้ามกับ EntityFramework.BulkInsert ห้องสมุดนี้ยังคงให้บริการฟรี +1
Rudey

14

Dispose()บริบทสร้างปัญหาหากเอนทิตีที่คุณAdd()พึ่งพาเอนทิตีที่โหลดไว้ล่วงหน้าอื่น ๆ (เช่นคุณสมบัติการนำทาง) ในบริบท

ฉันใช้แนวคิดที่คล้ายกันเพื่อให้บริบทของฉันเล็กเพื่อให้ได้ประสิทธิภาพเดียวกัน

แต่แทนที่จะDispose()บริบทและสร้างใหม่ฉันก็แยกเอนทิตี้ที่มีอยู่แล้วSaveChanges()

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 และTrasactionScope()หากคุณต้องการไม่แสดงที่นี่เพื่อรักษาโค้ดให้สะอาด


1
ที่ชะลอตัวแทรก (AddRange) โดยใช้ Entity Framework 6.0 การแทรกแถว 20,000 แถวเพิ่มขึ้นจากประมาณ 101 วินาทีเป็น 118 วินาที
อา

1
@ สตีเฟ่นโฮ: ฉันก็พยายามที่จะหลีกเลี่ยงบริบทของฉัน ฉันสามารถเข้าใจว่าสิ่งนี้ช้ากว่าการสร้างบริบทขึ้นใหม่ แต่ฉันต้องการทราบว่าคุณพบสิ่งนี้เร็วกว่าการสร้างบริบทขึ้นใหม่หรือไม่ แต่ด้วยชุด CommCount
ผู้เรียน

@Learner: ฉันคิดว่ามันเร็วกว่าการสร้างบริบทขึ้นมาใหม่ แต่ฉันจำไม่ได้จริงๆตอนนี้เพราะฉันเปลี่ยนมาใช้ SqlBulkCopy ในที่สุด
สตีเฟ่นโฮ

ฉันลงเอยด้วยการใช้เทคนิคนี้เพราะด้วยเหตุผลแปลก ๆ บางอย่างมีบางสิ่งที่เหลือในการติดตามที่เกิดขึ้นในการส่งผ่านครั้งที่สองผ่านขณะที่ลูปแม้ว่าฉันจะมีทุกสิ่งห่อหุ้มด้วยคำสั่งการใช้และเรียกว่า Dispose () บน DbContext . เมื่อฉันเพิ่มลงในบริบท (ในรอบที่สอง) การนับชุดบริบทจะข้ามไปที่ 6 แทนที่จะเป็นเพียงชุดเดียว รายการอื่น ๆ ที่เพิ่มเข้ามาโดยพลการถูกแทรกไว้ในการส่งผ่านครั้งแรกผ่านลูป while เพื่อที่การเรียกใช้ SaveChanges จะล้มเหลวในการส่งครั้งที่สอง (ด้วยเหตุผลที่ชัดเจน)
Hallmanac

9

ฉันรู้ว่านี่เป็นคำถามที่เก่ามาก แต่มีชายคนหนึ่งที่นี่บอกว่าได้พัฒนาวิธีการต่อขยายเพื่อใช้การแทรกแบบกลุ่มด้วย EF และเมื่อฉันตรวจสอบฉันค้นพบว่าห้องสมุดมีราคา 599 เหรียญสหรัฐในวันนี้ บางทีมันอาจสมเหตุสมผลสำหรับทั้งห้องสมุด แต่สำหรับการแทรกแบบกลุ่มจำนวนมากนี้มากเกินไป

นี่เป็นวิธีการขยายที่ง่ายมากที่ฉันทำ ฉันใช้คู่กับฐานข้อมูลก่อน (อย่าทดสอบด้วยรหัสก่อน แต่ฉันคิดว่ามันใช้งานได้เหมือนกัน) เปลี่ยนYourEntitiesด้วยชื่อบริบทของคุณ:

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

คุณสามารถใช้สิ่งนั้นกับคอลเลกชันที่สืบทอดมาIEnumerableเช่นนั้น:

await context.BulkInsertAllAsync(items);

กรุณากรอกรหัสตัวอย่างของคุณ ที่ไหนคือ bulkCopy
Seabizkit

1
มันมีอยู่แล้วที่นี่:await bulkCopy.WriteToServerAsync(table);
Guilherme

บางทีฉันไม่ชัดเจนในการเขียนของคุณคุณแนะนำให้คุณทำส่วนขยาย ... ซึ่งฉันเอาไปหมายความว่าไม่จำเป็นต้องใช้ส่วนที่ 3 lib ในความเป็นจริงในทั้งสองวิธีใช้ SqlBulkCopy lib สิ่งนี้ขึ้นอยู่กับ SqlBulkCopy เมื่อฉันถามว่า bulkCopy มาจากไหนส่วนขยายของ lib ที่คุณเขียน lib ส่วนขยายอยู่ด้านบน จะทำให้รู้สึกมากกว่าที่จะพูดที่นี่เป็นวิธีที่ฉันใช้ SqlBulkCopy lib
Seabizkit

ควรใช้ conn.OpenAsync ในเวอร์ชัน async
Robert

6

ลองใช้กระบวนงานที่เก็บไว้ซึ่งจะได้รับ XML ของข้อมูลที่คุณต้องการแทรก


9
การส่งผ่านข้อมูลเป็น XML ไม่จำเป็นถ้าคุณไม่ต้องการเก็บไว้เป็น XML ใน SQL 2008 คุณสามารถใช้พารามิเตอร์ที่มีค่าของตาราง
Ladislav Mrnka

ฉันไม่ได้ชี้แจงเรื่องนี้ แต่ฉันต้องสนับสนุน SQL 2005 ด้วย
Bongo Sharp

4

ฉันได้ขยายตัวอย่างทั่วไปของ @Slauma s ด้านบน

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

4

ฉันกำลังมองหาวิธีที่เร็วที่สุดในการแทรกลงใน Entity Framework

มีห้องสมุดบุคคลที่สามที่รองรับการแทรกเป็นกลุ่ม:

  • Z.EntityFramework.Extensions ( แนะนำ )
  • EFUtilities
  • EntityFramework.BulkInsert

โปรดดู: ไลบรารีการแทรกเฟรมเวิร์กของเอนทิตี

ระวังเมื่อเลือกไลบรารีแทรกจำนวนมาก เฉพาะ Entity Framework Extensions เท่านั้นที่รองรับการเชื่อมโยงและการสืบทอดทุกประเภทและยังคงรองรับการเชื่อมต่อเพียงรายการเดียว


ข้อจำกัดความรับผิดชอบ : ฉันเป็นเจ้าของส่วนขยายของEntity Framework

ไลบรารีนี้ช่วยให้คุณสามารถดำเนินการเป็นกลุ่มทั้งหมดที่คุณต้องการสำหรับสถานการณ์ของคุณ:

  • บันทึกการเปลี่ยนแปลงเป็นกลุ่ม
  • แทรกจำนวนมาก
  • ลบเป็นกลุ่ม
  • อัปเดตเป็นกลุ่ม
  • การรวมเป็นกลุ่ม

ตัวอย่าง

// 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
นี้เป็นส่วนขยายที่ดี แต่ไม่ฟรี
Okan Kocyigit

2
คำตอบนี้ค่อนข้างดีและEntityFramework.BulkInsertทำการแทรกแถวจำนวน 15K ใน 1.5 วินาทีใช้งานได้ดีสำหรับกระบวนการภายในเช่น Windows Service
ศิษยาภิบาล Cortes

4
ใช่ 600 $ สำหรับการแทรกจำนวนมาก Totaly คุ้มค่า
eocron

1
@eocron Yeat มันคุ้มค่าถ้าคุณใช้มันในเชิงพาณิชย์ ฉันไม่เห็นปัญหาใด ๆ กับ $ 600 สำหรับสิ่งที่ฉันไม่ต้องใช้เวลาในการสร้างด้วยตัวเองซึ่งจะทำให้ฉันมีราคามากกว่า $ 600 ใช่มันมีค่าใช้จ่าย แต่ดูที่อัตรารายชั่วโมงของฉันมันเป็นเงินที่ดีใช้จ่าย!
Jordy van Eijk

3

การใช้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

หนึ่งในวิธีที่เร็วที่สุดในการบันทึกรายการคุณต้องใช้รหัสต่อไปนี้

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

AutoDetectChangesEnabled = false

เพิ่มเพิ่มเปลี่ยน & บันทึกการเปลี่ยนแปลง: ตรวจไม่พบการเปลี่ยนแปลง

ValidateOnSaveEnabled = false;

ไม่พบตัวติดตามการเปลี่ยนแปลง

คุณต้องเพิ่ม nuget

Install-Package Z.EntityFramework.Extensions

ตอนนี้คุณสามารถใช้รหัสต่อไปนี้

var context = new MyContext();

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

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

ฉันสามารถใช้โค้ดตัวอย่างสำหรับการอัปเดตเป็นกลุ่มได้หรือไม่
AminGolmahalle

4
ไลบรารี Z ไม่ฟรี
SHADOW.NET

3

SqlBulkCopy เร็วสุด ๆ

นี่คือการดำเนินการของฉัน:

// 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] EF Core 3.1

หลังจากสิ่งที่กล่าวข้างต้นการปิดการใช้งาน AutoDetectChangesEnabled ใน EF Core ทำงานได้อย่างสมบูรณ์: เวลาการแทรกถูกหารด้วย 100 (จากหลายนาทีจนถึงไม่กี่วินาทีบันทึก 10k ที่มีความสัมพันธ์ข้ามตาราง)

รหัสที่อัพเดทคือ:

  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

นี่คือการเปรียบเทียบประสิทธิภาพระหว่างการใช้ Entity Framework และการใช้คลาส SqlBulkCopy ในตัวอย่างที่เป็นจริง: วิธีแทรกวัตถุที่ซับซ้อนเป็นกลุ่มลงในฐานข้อมูล SQL Server

ตามที่ผู้อื่นเน้นย้ำอยู่แล้ว ORM ไม่ได้มีวัตถุประสงค์เพื่อใช้ในการดำเนินงานจำนวนมาก พวกเขามีความยืดหยุ่นการแยกข้อกังวลและผลประโยชน์อื่น ๆ แต่การดำเนินการจำนวนมาก (ยกเว้นการอ่านจำนวนมาก) ไม่ใช่หนึ่งในนั้น


2

อีกตัวเลือกหนึ่งคือการใช้ SqlBulkTools จาก Nuget มันใช้งานง่ายมากและมีคุณสมบัติที่ทรงพลังบางอย่าง

ตัวอย่าง:

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

ดูเอกสารประกอบสำหรับตัวอย่างเพิ่มเติมและการใช้งานขั้นสูง คำเตือน: ฉันเป็นผู้เขียนของห้องสมุดนี้และมุมมองใด ๆ ที่มีความเห็นของฉันเอง


2
โครงการนี้ถูกลบออกจากทั้ง NuGet และ GitHub
0xced

1

ตามความรู้ของฉันมีno BulkInsertในEntityFrameworkการเพิ่มประสิทธิภาพของเม็ดมีดขนาดใหญ่

ในสถานการณ์นี้คุณสามารถใช้SqlBulkCopyในADO.netเพื่อแก้ปัญหาของคุณ


ฉันดูคลาสนั้น แต่ดูเหมือนว่าจะเน้นไปที่การแทรกแบบตารางต่อตารางมากขึ้นใช่ไหม
Bongo Sharp

ไม่แน่ใจว่าสิ่งที่คุณหมายถึงมันมีมากเกินไปที่จะนำWriteToServer DataTable
Blindy

ไม่คุณสามารถแทรกจากวัตถุ. Net ไปยัง SQL ได้ด้วยคุณกำลังมองหาอะไร?
anishMarokey

วิธีการใส่ที่อาจเกิดขึ้นอย่างมากมายระเบียนในฐานข้อมูลภายในบล็อก TransactionScope
กลองชาร์ป

คุณสามารถใช้สุทธิ TransactionScope technet.microsoft.com/en-us/library/bb896149.aspx
anishMarokey

1

คุณเคยพยายามแทรกผ่านผู้ทำงานเบื้องหลังหรืองาน?

ในกรณีของฉันฉันใส่ลงทะเบียน 7760 กระจายในตารางที่แตกต่างกัน 182 ตารางที่มีความสัมพันธ์กับต่างประเทศที่สำคัญ (ตาม NavigationProperties)

ถ้าไม่มีงานก็ใช้เวลา 2 นาทีครึ่ง ภายในงาน ( Task.Factory.StartNew(...)) ใช้เวลา 15 วินาที

ฉันแค่ทำSaveChanges()หลังจากที่เพิ่มเอนทิตีทั้งหมดในบริบท (เพื่อรับรองความถูกต้องของข้อมูล)


2
ฉันค่อนข้างมั่นใจว่าบริบทไม่ปลอดภัยสำหรับเธรด คุณมีการทดสอบเพื่อให้แน่ใจว่าเอนทิตีทั้งหมดถูกบันทึกหรือไม่
Danny Varod

ฉันรู้ว่าเอนทิตี้ของเฟรมเวิร์กทั้งหมดไม่ใช่เธรดที่ปลอดภัยเลย แต่ฉันแค่เพิ่มวัตถุในบริบทและบันทึกในตอนท้าย ... มันทำงานได้อย่างสมบูรณ์ที่นี่
Rafael AMS

ดังนั้นคุณกำลังเรียก DbContext.SaveChanges () ในเธรดหลัก แต่การเพิ่มเอนทิตีกับบริบทจะดำเนินการในเธรดพื้นหลังใช่ไหม
Prokurors

1
ใช่เพิ่มข้อมูลภายในเธรด รอให้ทุกคนเสร็จสิ้น และบันทึกการเปลี่ยนแปลงในเธรดหลัก
Rafael AMS

แม้ว่าฉันคิดว่าวิธีนี้จะอันตรายและมีแนวโน้มที่จะเกิดข้อผิดพลาด แต่ฉันคิดว่ามันน่าสนใจมาก
ผู้เรียน

1

โซลูชันทั้งหมดที่เขียนที่นี่ไม่ได้ช่วยเพราะเมื่อคุณทำ SaveChanges () งบแทรกจะถูกส่งไปยังฐานข้อมูลทีละคนนั่นเป็นวิธีที่ Entity ทำงาน

และหากการเดินทางไปยังฐานข้อมูลและด้านหลังของคุณคือ 50 ms ดังนั้นเวลาที่ใช้ในการแทรกคือจำนวนเรคคอร์ด x 50 ms

คุณต้องใช้ BulkInsert นี่คือลิงค์: https://efbulkinsert.codeplex.com/

ฉันได้เวลาใส่ลดลงจาก 5-6 นาทีเป็น 10-12 วินาทีโดยใช้มัน



1

[โซลูชั่นใหม่สำหรับ POSTGRESQL] เฮ้ฉันรู้ว่ามันค่อนข้างโพสต์เก่า แต่เมื่อเร็ว ๆ นี้ฉันพบปัญหาที่คล้ายกัน แต่เราใช้ Postgresql ฉันต้องการใช้กลุ่มเอกสารที่มีประสิทธิภาพสิ่งที่กลายเป็นเรื่องยาก ฉันไม่ได้พบห้องสมุดฟรีที่เหมาะสมที่จะทำในฐานข้อมูลนี้ ฉันพบผู้ช่วยนี้เท่านั้น: https://bytefish.de/blog/postgresql_bulk_insert/ ซึ่งอยู่ใน Nuget ด้วย ฉันได้เขียน mapper ขนาดเล็กซึ่งคุณสมบัติการแมปอัตโนมัติวิธี 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;
        }

ฉันใช้วิธีต่อไปนี้ (ฉันมีเอนทิตีที่ชื่อ Undertaking):

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

ฉันแสดงตัวอย่างพร้อมธุรกรรม แต่ก็สามารถทำได้ด้วยการเชื่อมต่อปกติที่ดึงมาจากบริบท undertakingsTo เพิ่มเป็นจำนวนมากของเอนทิตีบันทึกปกติซึ่งฉันต้องการ bulkInsert ลงในฐานข้อมูล

วิธีแก้ปัญหานี้ซึ่งฉันได้รับหลังจากการค้นคว้าและทดลองใช้ไม่กี่ชั่วโมงนั้นเป็นไปตามที่คุณคาดหวังได้เร็วกว่าและในที่สุดใช้งานง่ายและฟรี! ฉันแนะนำให้คุณใช้โซลูชันนี้จริงๆไม่เพียง แต่ด้วยเหตุผลที่กล่าวถึงข้างต้น แต่ยังเป็นเพราะเป็นโปรแกรมเดียวที่ฉันไม่มีปัญหากับ Postgresql เองโซลูชันอื่น ๆ อีกมากมายทำงานได้อย่างไร้ที่ติเช่น SqlServer


0

ความลับคือการแทรกลงในตารางว่างเปล่าที่เหมือนกัน เม็ดมีดลดน้ำหนักอย่างรวดเร็ว จากนั้นเรียกใช้การแทรกครั้งเดียวจากที่ลงในตารางขนาดใหญ่หลักของคุณ จากนั้นตัดทอนตารางเตรียมฉากสำหรับชุดถัดไป

กล่าวคือ

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 เพิ่มระเบียนทั้งหมดของคุณลงในตารางเตรียมการเปล่า จากนั้นใช้ SQL เพื่อแทรกลงในตารางหลัก (ใหญ่และช้า) ในคำสั่ง SQL เดี่ยว จากนั้นล้างตารางการเตรียมของคุณ มันเป็นวิธีที่รวดเร็วในการแทรกข้อมูลจำนวนมากลงในตารางที่มีขนาดใหญ่อยู่แล้ว
Simon Hughes

13
เมื่อคุณพูดโดยใช้ EF ให้เพิ่มระเบียนลงในตารางการแสดงละครคุณลองใช้ EF จริงหรือไม่ เนื่องจาก EF ออกการเรียกไปยังฐานข้อมูลแยกต่างหากด้วยการแทรกแต่ละครั้งฉันจึงสงสัยว่าคุณจะเห็นการตีที่สมบูรณ์แบบที่ OP พยายามหลีกเลี่ยง ตารางการจัดเตรียมจะหลีกเลี่ยงปัญหานี้ได้อย่างไร
Jim Wooley

-1

แต่สำหรับส่วนแทรกมากกว่า (+000) ผมแนะนำให้ใช้โพรซีเดอร์ที่เก็บไว้ แนบเวลาที่ผ่านไป ฉันใส่มัน 11.788 แถวใน 20 "ป้อนคำอธิบายรูปภาพที่นี่

มันคือรหัส

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

-1

ใช้โพรซีเดอร์ที่เก็บไว้ซึ่งรับข้อมูลอินพุตในรูปแบบของ xml เพื่อแทรกข้อมูล

จากข้อมูล c # code pass ของคุณให้แทรกเป็น xml

เช่นใน c # ไวยากรณ์จะเป็นดังนี้:

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

-7

ใช้เทคนิคนี้เพื่อเพิ่มความเร็วในการแทรกระเบียนใน Entity Framework ที่นี่ฉันใช้ขั้นตอนง่าย ๆ ที่เก็บไว้เพื่อแทรกระเบียน และเพื่อดำเนินการขั้นตอนการจัดเก็บนี้ฉันใช้. FromSql () วิธีการของ Entity Frameworkซึ่งดำเนินการ SQL ดิบ

รหัสขั้นตอนการจัดเก็บ:

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

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

ถัดไปวนรอบระเบียนทั้งหมด4000 รายการของคุณและเพิ่มรหัส Framework เอนทิตีซึ่งดำเนินการจัดเก็บ

โพรซีเดอร์ onces ทุกๆ 100th loop

สำหรับสิ่งนี้ฉันสร้างเคียวรีสตริงเพื่อเรียกใช้โพรซีเดอร์นี้ให้ต่อท้ายทุกเร็กคอร์ดต่อท้าย

จากนั้นตรวจสอบห่วงทำงานในทวีคูณของ 100 .FromSql()และในกรณีที่ดำเนินการโดยใช้

ดังนั้นสำหรับ 4000 บันทึกฉันมีเพียงแค่การดำเนินการตามขั้นตอนในราคาเพียง 4000/100 = 40 ครั้ง

ตรวจสอบรหัสด้านล่าง:

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

สิ่งนี้อาจมีประสิทธิภาพ แต่ไม่เท่ากับการใช้เฟรมเวิร์กเอนทิตี คำถาม OP คือวิธีการเพิ่มประสิทธิภาพในบริบทของ Entity Framework
kall2sollies
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.