เลิกทำการเปลี่ยนแปลงในเอนทิตีกรอบงานเอนทิตี


116

นี่อาจเป็นคำถามเล็กน้อย แต่: เนื่องจากเฟรมเวิร์กเอนทิตี ADO.NET ติดตามการเปลี่ยนแปลงโดยอัตโนมัติ (ในเอนทิตีที่สร้างขึ้น) ดังนั้นจึงเก็บค่าดั้งเดิมไว้ฉันจะย้อนกลับการเปลี่ยนแปลงที่ทำกับเอนทิตีอ็อบเจ็กต์ได้อย่างไร

ฉันมีแบบฟอร์มที่อนุญาตให้ผู้ใช้แก้ไขชุดของเอนทิตี "ลูกค้า" ในมุมมองกริด

ตอนนี้ฉันมีสองปุ่ม "ยอมรับ" และ "เปลี่ยนกลับ": ถ้าคลิก "ยอมรับ" ฉันจะเรียกContext.SaveChanges()และวัตถุที่เปลี่ยนแปลงจะถูกเขียนกลับไปที่ฐานข้อมูล หากคลิก "เปลี่ยนกลับ" ฉันต้องการให้วัตถุทั้งหมดได้รับค่าคุณสมบัติดั้งเดิม รหัสสำหรับสิ่งนั้นคืออะไร?

ขอบคุณ

คำตอบ:


69

ไม่มีการเปลี่ยนกลับหรือยกเลิกการดำเนินการเปลี่ยนแปลงใน EF แต่ละนิติบุคคลมีObjectStateEntryในObjectStateManager. รายการสถานะประกอบด้วยค่าดั้งเดิมและค่าจริงดังนั้นคุณสามารถใช้ค่าดั้งเดิมเพื่อเขียนทับค่าปัจจุบันได้ แต่คุณต้องทำด้วยตนเองสำหรับแต่ละเอนทิตี จะไม่เปิดเผยการเปลี่ยนแปลงคุณสมบัติ / ความสัมพันธ์ของการนำทาง

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


4
@LadislavMrnka แน่นอนว่าContext.Refresh()เป็นตัวอย่างที่ต่อต้านการอ้างสิทธิ์ของคุณว่าไม่มีการดำเนินการคืนกลับ? การใช้Refresh()ดูเหมือนจะเป็นแนวทางที่ดีกว่า (กล่าวคือกำหนดเป้าหมายได้ง่ายกว่าในหน่วยงานเฉพาะ) มากกว่าการกำจัดบริบทและสูญเสียการเปลี่ยนแปลงที่ติดตามทั้งหมด
Rob

14
@robjb: ไม่รีเฟรชสามารถรีเฟรชเฉพาะเอนทิตีเดียวหรือคอลเลกชันของเอนทิตีที่คุณกำหนดด้วยตนเอง แต่ฟังก์ชันการรีเฟรชจะส่งผลต่อคุณสมบัติธรรมดาเท่านั้น (ไม่ใช่ความสัมพันธ์) นอกจากนี้ยังไม่แก้ปัญหากับเอนทิตีที่เพิ่มหรือลบ
Ladislav Mrnka

153

สอบถาม ChangeTracker ของ DbContext สำหรับรายการสกปรก ตั้งค่าสถานะรายการที่ถูกลบเป็นรายการที่ไม่เปลี่ยนแปลงและเพิ่มเป็นรายการที่แยกออก สำหรับรายการที่แก้ไขให้ใช้ค่าดั้งเดิมและตั้งค่าปัจจุบันของรายการ สุดท้ายตั้งค่าสถานะของรายการที่แก้ไขเป็นไม่เปลี่ยนแปลง:

public void RollBack()
{
    var context = DataContextFactory.GetDataContext();
    var changedEntries = context.ChangeTracker.Entries()
        .Where(x => x.State != EntityState.Unchanged).ToList();

    foreach (var entry in changedEntries)
    {
        switch(entry.State)
        {
            case EntityState.Modified:
                entry.CurrentValues.SetValues(entry.OriginalValues);
                entry.State = EntityState.Unchanged;
                break;
            case EntityState.Added:
                entry.State = EntityState.Detached;
                break;
            case EntityState.Deleted:
                entry.State = EntityState.Unchanged;
                break;
        }
    }
 }

3
ขอบคุณ - สิ่งนี้ช่วยฉันได้จริงๆ!
Matt

5
คุณควรตั้งค่าเดิมเป็นรายการที่ถูกลบเช่นกัน เป็นไปได้ที่คุณจะเปลี่ยนรายการก่อนและลบออกหลังจากนั้น
Bas de Raad

22
การตั้งค่าStateเป็นEntityState Unchangedจะแทนที่ค่าทั้งหมดด้วยOriginal Valuesดังนั้นจึงไม่จำเป็นต้องเรียกSetValuesเมธอด
Abolfazl Hosnoddin

10
คำตอบนี้เวอร์ชันที่สะอาดกว่า: stackoverflow.com/a/22098063/2498426
Jerther

1
เมทนี่สุดยอด! การปรับเปลี่ยนเท่านั้นที่ฉันทำคือการใช้ Entries <T> () เวอร์ชันทั่วไปเพื่อให้เหมาะกับการหายใจของฉัน สิ่งนี้ทำให้ฉันควบคุมได้มากขึ้นและฉันสามารถย้อนกลับตามประเภทเอนทิตีได้ ขอบคุณ!
Daniel Mackay

33
dbContext.Entry(entity).Reload();

สอดคล้องกับMSDN :

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

โปรดทราบว่าการคืนค่าผ่านการร้องขอไปยังฐานข้อมูลมีข้อบกพร่องบางประการ:

  • การรับส่งข้อมูลเครือข่าย
  • DB เกิน
  • เวลาตอบสนองของแอปพลิเคชันที่เพิ่มขึ้น

17

สิ่งนี้ใช้ได้ผลสำหรับฉัน:

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

ในกรณีที่itemเป็นนิติบุคคลของลูกค้าที่จะหวนกลับ


12

วิธีง่ายๆโดยไม่ต้องติดตามการเปลี่ยนแปลงใด ๆ ควรเร็วกว่าการดูทุกเอนทิตี

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}

เวลาในการสร้างอ็อบเจ็กต์การให้สิทธิ์เดียว ... ซึ่งเป็นสองสามมิลลิวินาที (50 มิลลิวินาที) การวนซ้ำในคอลเล็กชันอาจเร็วขึ้นหรือนานขึ้นขึ้นอยู่กับขนาด ประสิทธิภาพที่ชาญฉลาด O (1) ไม่ค่อยมีปัญหาเมื่อเทียบกับ O (n) Big O notation
Guish

ไม่ติดตามคุณ - ประสิทธิภาพในการกำจัดและสร้างการเชื่อมต่อใหม่ ฉันทดสอบกับโครงการที่มีอยู่และเสร็จสิ้นเร็วกว่าRollbackขั้นตอนข้างต้นซึ่งทำให้เป็นทางเลือกที่ดีกว่ามากหากต้องการเปลี่ยนสถานะฐานข้อมูลทั้งหมด การย้อนกลับสามารถเลือกเชอร์รี่ได้
majkinetor

'n' หมายถึงจำนวนวัตถุ การสร้างการเชื่อมต่อใหม่ใช้เวลาประมาณ 50 มิลลิวินาที ... O (1) หมายถึงเวลาเดียวกัน50ms+0*n= 50msเสมอ O (n) หมายถึงประสิทธิภาพได้รับอิทธิพลจากจำนวนของวัตถุ ... ประสิทธิภาพอาจจะ2ms+0.5ms*n... ดังนั้นการร้อง 96 วัตถุมันจะเร็วกว่า แต่เวลาจะเพิ่มขึ้นในเชิงเส้นตามปริมาณข้อมูล
Guish

หากคุณจะไม่เลือกเชอร์รี่ว่าอะไร (ไม่) ย้อนกลับนี่คือวิธีที่จะไปหากคุณไม่กังวลเกี่ยวกับแบนด์วิดท์
Anthony Nichols

6
// Undo the changes of all entries. 
foreach (DbEntityEntry entry in context.ChangeTracker.Entries()) 
{ 
    switch (entry.State) 
    { 
        // Under the covers, changing the state of an entity from  
        // Modified to Unchanged first sets the values of all  
        // properties to the original values that were read from  
        // the database when it was queried, and then marks the  
        // entity as Unchanged. This will also reject changes to  
        // FK relationships since the original value of the FK  
        // will be restored. 
        case EntityState.Modified: 
            entry.State = EntityState.Unchanged; 
            break; 
        case EntityState.Added: 
            entry.State = EntityState.Detached; 
            break; 
        // If the EntityState is the Deleted, reload the date from the database.   
        case EntityState.Deleted: 
            entry.Reload(); 
            break; 
        default: break; 
    } 
} 

มันได้ผลสำหรับฉัน อย่างไรก็ตามคุณต้องโหลดข้อมูลใหม่จากบริบทเพื่อนำข้อมูลเก่า แหล่งที่มาที่นี่


3

"สิ่งนี้ได้ผลสำหรับฉัน:

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

ในกรณีที่itemเป็นนิติบุคคลของลูกค้าที่จะหวนกลับไป."


ฉันได้ทำการทดสอบด้วย ObjectContext.Refresh ใน SQL Azure และ "RefreshMode.StoreWins" เริ่มการสอบถามกับฐานข้อมูลสำหรับแต่ละเอนทิตีและทำให้ประสิทธิภาพการทำงานรั่วไหล อ้างอิงจากเอกสารของ Microsoft ():

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

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

ClientWins ไม่ใช่แนวคิดที่ดีเช่นกันเนื่องจากการเริ่มทำงาน. SaveChanges จะทำการเปลี่ยนแปลง "ทิ้ง" ไปยังแหล่งข้อมูล

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

ความนับถือ,

Henrique Clausing


2

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


4
หมายเหตุ: การเปลี่ยนแปลงจะกลับมาหากมีการเปลี่ยนแปลงเอนทิตีอีกครั้ง
Nick Whaley

2

ฉันพบว่าสิ่งนี้ทำงานได้ดีในบริบทของฉัน:

Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);


1
ฉันเชื่อว่าสิ่งนี้จะป้องกันไม่ให้การเปลี่ยนแปลงในเอนทิตีคงอยู่เมื่อมีการโทรDbContext.SaveChanges()แต่จะไม่คืนค่าเอนทิตีกลับเป็นค่าดั้งเดิม และถ้าสถานะของเอนทิตีถูกแก้ไขจากการเปลี่ยนแปลงในภายหลังการแก้ไขก่อนหน้านี้ทั้งหมดจะยังคงอยู่เมื่อบันทึก?
Carl G

1
ตรวจสอบลิงก์นี้code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4ซึ่งระบุว่าการตั้งค่าเอนทิตีเป็นสถานะที่ไม่ได้ตั้งค่าจะคืนค่าเดิม "ภายใต้ฝาครอบ"
Hannish

2

นี่คือตัวอย่างของสิ่งที่ Mrnka กำลังพูดถึง วิธีการต่อไปนี้จะเขียนทับค่าปัจจุบันของเอนทิตีด้วยค่าดั้งเดิมและไม่เรียกฐานข้อมูลออก เราทำสิ่งนี้โดยการใช้คุณสมบัติ OriginalValues ​​ของ DbEntityEntry และใช้การสะท้อนเพื่อกำหนดค่าในลักษณะทั่วไป (ใช้ได้กับ EntityFramework 5.0)

/// <summary>
/// Undoes any pending updates 
/// </summary>
public void UndoUpdates( DbContext dbContext )
{
    //Get list of entities that are marked as modified
    List<DbEntityEntry> modifiedEntityList = 
        dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).ToList();

    foreach(  DbEntityEntry entity in modifiedEntityList ) 
    {
        DbPropertyValues propertyValues = entity.OriginalValues;
        foreach (String propertyName in propertyValues.PropertyNames)
        {                    
            //Replace current values with original values
            PropertyInfo property = entity.Entity.GetType().GetProperty(propertyName);
            property.SetValue(entity.Entity, propertyValues[propertyName]); 
        }
    }
}

1

เรากำลังใช้ EF 4 กับบริบท Legacy Object ไม่มีวิธีแก้ปัญหาใด ๆ ข้างต้นที่ตอบคำถามนี้โดยตรงสำหรับฉัน - แม้ว่ามันจะไม่ตอบโจทย์นี้ในระยะยาวโดยการผลักดันฉันไปในทิศทางที่ถูกต้อง

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

ด้านล่างนี้คือวิธีแก้ปัญหาเดียวกันของเรา:

    public static void UndoAllChanges(OurEntities ctx)
    {
        foreach (ObjectStateEntry entry in
            ctx.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached))
        {
            if (entry.State != EntityState.Unchanged)
            {
                ctx.Refresh(RefreshMode.StoreWins, entry.Entity);
            }
        }
    }

ฉันหวังว่านี่จะช่วยคนอื่น ๆ


0

แนวคิดดีๆข้างต้นฉันเลือกที่จะใช้ ICloneable และวิธีการขยายแบบง่ายๆ

พบที่นี่: ฉันจะโคลนรายการทั่วไปใน C # ได้อย่างไร

เพื่อใช้เป็น:

ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);

ด้วยวิธีนี้ฉันสามารถโคลนรายการเอนทิตีผลิตภัณฑ์ของฉันใช้ส่วนลดกับแต่ละรายการและไม่ต้องกังวลเกี่ยวกับการคืนค่าการเปลี่ยนแปลงใด ๆ ในเอนทิตีดั้งเดิม ไม่จำเป็นต้องพูดคุยกับ DBContext และขอการรีเฟรชหรือทำงานกับ ChangeTracker คุณอาจบอกว่าฉันไม่ได้ใช้ EF6 อย่างเต็มที่ แต่นี่เป็นการใช้งานที่ดีและง่ายมากและหลีกเลี่ยงการเข้าสู่ DB ฉันไม่สามารถพูดได้ว่าสิ่งนี้มีผลงานหรือไม่

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