ใช้การทำธุรกรรมหรือ SaveChanges (false) และ AcceptAllChanges ()?


346

ฉันตรวจสอบธุรกรรมและดูเหมือนว่าพวกเขาดูแลตัวเองใน EF ตราบใดที่ฉันผ่านfalseไปSaveChanges()แล้วโทรติดต่อAcceptAllChanges()หากไม่มีข้อผิดพลาด:

SaveChanges(false);
// ...
AcceptAllChanges();

เกิดอะไรขึ้นถ้ามีบางอย่างไม่ดี? ฉันไม่ต้องย้อนกลับหรือทันทีที่วิธีการของฉันไม่อยู่ในขอบเขตการทำธุรกรรมสิ้นสุดลงหรือไม่

เกิดอะไรขึ้นกับคอลัมน์ indentiy ใด ๆ ที่ได้รับมอบหมายผ่านทางธุรกรรมครึ่งทาง ฉันคิดว่าถ้ามีคนเพิ่มบันทึกหลังจากฉันก่อนที่ฉันจะเสียก็หมายความว่าจะมีค่าประจำตัวที่ขาดหายไป

มีเหตุผลใดที่จะใช้TransactionScopeคลาสมาตรฐานในรหัสของฉัน?


1
สิ่งนี้ช่วยให้ฉันเข้าใจว่าทำไมSaveChanges(fase); ... AcceptAllChanges();รูปแบบในตอนแรก โปรดสังเกตว่าคำตอบที่ได้รับการยอมรับสำหรับคำถามข้างต้นเขียนโดยผู้เขียนบล็อก และบล็อกนั้นอ้างอิงในคำถามอื่น ทุกอย่างมารวมกัน
ถั่วแดง

คำตอบ:


451

ด้วย Entity Framework เวลาส่วนใหญ่SaveChanges()ก็เพียงพอแล้ว สิ่งนี้จะสร้างทรานแซคชันหรือ enlist ในทรานแซคชันใด ๆ และทำงานที่จำเป็นทั้งหมดในทรานแซคชันนั้น

บางครั้งแม้ว่าการSaveChanges(false) + AcceptAllChanges()จับคู่จะมีประโยชน์

สถานที่ที่มีประโยชน์ที่สุดสำหรับสิ่งนี้คือในสถานการณ์ที่คุณต้องการทำธุรกรรมแบบกระจายในบริบทที่แตกต่างกันสองแห่ง

เช่นนี้ (ไม่ดี):

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save and discard changes
    context1.SaveChanges();

    //Save and discard changes
    context2.SaveChanges();

    //if we get here things are looking good.
    scope.Complete();
}

หาก context1.SaveChanges()ประสบความสำเร็จ แต่context2.SaveChanges()ล้มเหลวการทำธุรกรรมการกระจายทั้งหมดจะถูกยกเลิก แต่น่าเสียดายที่ Entity Framework ยกเลิกการเปลี่ยนแปลงไปcontext1แล้วดังนั้นคุณจึงไม่สามารถเล่นซ้ำหรือบันทึกความล้มเหลวได้อย่างมีประสิทธิภาพ

แต่ถ้าคุณเปลี่ยนรหัสของคุณเป็นดังนี้:

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save Changes but don't discard yet
    context1.SaveChanges(false);

    //Save Changes but don't discard yet
    context2.SaveChanges(false);

    //if we get here things are looking good.
    scope.Complete();
    context1.AcceptAllChanges();
    context2.AcceptAllChanges();

}

ในขณะที่การเรียกเพื่อSaveChanges(false)ส่งคำสั่งที่จำเป็นไปยังฐานข้อมูลบริบทจะไม่เปลี่ยนแปลงดังนั้นคุณสามารถทำได้อีกครั้งหากจำเป็นหรือคุณสามารถสอบถามObjectStateManagerหากคุณต้องการ

ซึ่งหมายความว่าถ้าการทำธุรกรรมเกิดข้อยกเว้นจริงคุณสามารถชดเชยได้โดยลองใหม่หรือเข้าสู่ระบบสถานะของแต่ละบริบทObjectStateManagerที่ไหนสักแห่ง

ดูโพสต์บล็อกของฉัน มากขึ้น


3
ขอบคุณมาก ... ถ้ามีอะไรที่ล้มเหลวฉันไม่ต้องย้อนกลับไป ?? SaveChanges ทำเครื่องหมายเพื่อการบันทึก แต่ไม่ได้ทำจริงจนกว่าฉันจะยอมรับการเปลี่ยนแปลงทั้งหมด .. แต่ถ้ามีบางอย่างผิดปกติ .. ฉันจะต้องย้อนกลับไม่ได้ฉันจะทำให้วัตถุของฉันกลับสู่สถานะที่ถูกต้องหรือไม่
ทำเครื่องหมายสมิ ธ

33
@ Mark: หาก "ย้อนกลับ" คุณหมายถึงเปลี่ยนวัตถุของคุณกลับสู่สถานะที่อยู่ในฐานข้อมูลแล้วไม่คุณไม่ต้องการทำเช่นนั้นเพราะคุณสูญเสียการเปลี่ยนแปลงวัตถุทั้งหมดของผู้ใช้ . SaveChanges(false)การอัปเดตฐานข้อมูลจริงหรือไม่ในขณะที่AcceptAllChanges()บอก EF ว่า "โอเคคุณสามารถลืมสิ่งที่ต้องบันทึกเพราะสิ่งเหล่านั้นได้รับการบันทึกไว้อย่างเพียงพอ" หากSaveChanges(false)ล้มเหลวAcceptAllChanges()จะไม่ถูกเรียกใช้และ EF จะยังคงพิจารณาออบเจ็กต์ของคุณว่ามีคุณสมบัติที่เปลี่ยนแปลงและจำเป็นต้องบันทึกกลับไปยังฐานข้อมูล
BlueRaja - Danny Pflughoeft

คุณสามารถแนะนำวิธีการใช้รหัสก่อนได้หรือไม่ ไม่มีพารามิเตอร์ในการบันทึกการเปลี่ยนแปลงหรือวิธีการยอมรับทั้งหมด
Kirsten Greed

2
ฉันได้ถามคำถามเกี่ยวกับการใช้เทคนิคนี้กับ Code First ที่นี่
Kirsten Greed

13
สิ่งนี้ไม่สามารถทำได้ใน EF 6.1 คุณรู้หรือไม่ว่าการปรับเปลี่ยนประเภทใดที่จำเป็นต้องใช้ในการทำงานในตอนนี้?
Alex Dresko

113

หากคุณใช้ EF6 (Entity Framework 6+) สิ่งนี้จะเปลี่ยนไปสำหรับการเรียกฐานข้อมูลไปยัง SQL
ดู: http://msdn.microsoft.com/en-us/data/dn456843.aspx

ใช้ context.Database.BeginTransaction

จาก MSDN:

using (var context = new BloggingContext()) 
{ 
    using (var dbContextTransaction = context.Database.BeginTransaction()) 
    { 
        try 
        { 
            context.Database.ExecuteSqlCommand( 
                @"UPDATE Blogs SET Rating = 5" + 
                    " WHERE Name LIKE '%Entity Framework%'" 
                ); 

            var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
            foreach (var post in query) 
            { 
                post.Title += "[Cool Blog]"; 
            } 

            context.SaveChanges(); 

            dbContextTransaction.Commit(); 
        } 
        catch (Exception) 
        { 
            dbContextTransaction.Rollback(); //Required according to MSDN article 
            throw; //Not in MSDN article, but recommended so the exception still bubbles up
        } 
    } 
} 

52
ไม่จำเป็นต้องใช้ try-catch กับ roolback เมื่อคุณใช้ "using" ในการทำธุรกรรม
Robert

12
ฉันกำลังยกเว้นเพื่อดักข้อยกเว้นเช่นนี้ ทำให้การดำเนินการฐานข้อมูลล้มเหลวอย่างเงียบ ๆ เนื่องจากลักษณะของ SO บางคนอาจใช้ตัวอย่างนี้และใช้ในแอปพลิเคชันที่ใช้งานจริง
B2K

3
@ B2K: จุดดี แต่รหัสนี้คัดลอกมาจากลิงค์บทความ Microsoft ที่ฉันหวังว่าไม่มีใครใช้พวกเขารหัสในการผลิต :)
เจไบรอันราคา

6
@Robert ตามบทความ MSDN Rollback () เป็นสิ่งที่จำเป็น พวกเขาตั้งใจที่จะออกคำสั่งย้อนกลับสำหรับตัวอย่าง TransactionScope @ B2K ฉันได้เพิ่มในthrow;ข้อมูลโค้ด MSDN และระบุไว้อย่างชัดเจนว่าไม่ใช่ต้นฉบับจากบทความ MSDN
ทอดด์

6
(หากถูกต้อง) สิ่งนี้อาจทำให้ชัดเจน:ดูเหมือนว่า EF + MSSQL ไม่จำเป็นต้องย้อนกลับ แต่ EF + ผู้ให้บริการ SQL รายอื่นอาจ เนื่องจาก EF ควรเป็นผู้ไม่เชื่อในฐานข้อมูลที่กำลังคุยRollback()อยู่จึงถูกเรียกใช้ในกรณีที่พูดกับ MySql หรือสิ่งที่ไม่มีพฤติกรรมอัตโนมัติ
คำเหมือน Jared

-5

เนื่องจากฐานข้อมูลบางตัวสามารถส่งข้อยกเว้นได้ที่ dbContextTransaction.Commit () ดีกว่านี้:

using (var context = new BloggingContext()) 
{ 
  using (var dbContextTransaction = context.Database.BeginTransaction()) 
  { 
    try 
    { 
      context.Database.ExecuteSqlCommand( 
          @"UPDATE Blogs SET Rating = 5" + 
              " WHERE Name LIKE '%Entity Framework%'" 
          ); 

      var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
      foreach (var post in query) 
      { 
          post.Title += "[Cool Blog]"; 
      } 

      context.SaveChanges(false); 

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
} 

7
ฉันกำลังยกเว้นเพื่อดักข้อยกเว้นเช่นนี้ ทำให้การดำเนินการฐานข้อมูลล้มเหลวอย่างเงียบ ๆ เนื่องจากลักษณะของ SO บางคนอาจใช้ตัวอย่างนี้และใช้ในแอปพลิเคชันที่ใช้งานจริง
B2K

6
นี่ไม่ใช่สิ่งเดียวกับคำตอบอื่น ๆที่ให้การระบุแหล่งที่มาไปยังหน้า MSDN ที่อ้างอิง ข้อแตกต่างที่ผมเห็นก็คือว่าคุณผ่านfalseเข้าไปและยังเรียกcontext.SaveChanges(); context.AcceptAllChanges();
Wai Ha Lee

@ B2K ไม่จำเป็นต้องย้อนกลับ - หากการทำธุรกรรมไม่ทำงานไม่มีอะไรเกิดขึ้น นอกจากนี้ยังเรียกร้องที่ชัดเจนในการย้อนกลับสามารถล้มเหลว - ดูคำตอบของฉันที่นี่stackoverflow.com/questions/41385740/...
เคน

การย้อนกลับไม่ใช่สิ่งที่ฉันคัดค้าน ผู้เขียนคำตอบนี้ได้อัปเดตรหัสของพวกเขาเพื่อสร้างข้อยกเว้นใหม่ดังนั้นจึงแก้ไขสิ่งที่ฉันคัดค้าน
B2K

ขออภัยฉันแสดงความคิดเห็นจากโทรศัพท์ของฉัน ทอดด์โยนข้อยกเว้นอีกครั้ง eMeL ไม่ได้ ควรมีบางสิ่งที่จับได้ซึ่งแจ้งให้ผู้พัฒนาหรือผู้ใช้ทราบถึงปัญหาที่ทำให้เกิดการย้อนกลับ นั่นอาจเป็นการเขียนไปยังล็อกไฟล์การสร้างข้อยกเว้นซ้ำหรือส่งคืนข้อความให้กับผู้ใช้
B2K
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.