Hoàn tác các thay đổi trong các thực thể khung thực thể


116

đây có thể là một câu hỏi tầm thường nhưng: Vì khung thực thể ADO.NET tự động theo dõi các thay đổi (trong các thực thể được tạo) và do đó giữ các giá trị ban đầu, làm cách nào để tôi có thể khôi phục các thay đổi được thực hiện đối với các đối tượng thực thể?

Tôi có một biểu mẫu cho phép người dùng chỉnh sửa tập hợp các thực thể "Khách hàng" trong chế độ xem lưới.

Bây giờ tôi có hai nút "Chấp nhận" và "Hoàn nguyên": nếu "Chấp nhận" được nhấp vào, tôi gọi Context.SaveChanges()và các đối tượng đã thay đổi được ghi trở lại cơ sở dữ liệu. Nếu "Hoàn nguyên" được nhấp vào, tôi muốn tất cả các đối tượng nhận được giá trị thuộc tính ban đầu của chúng. Mã cho điều đó sẽ là gì?

Cảm ơn

Câu trả lời:


69

Không có thao tác hoàn nguyên hoặc hủy thay đổi trong EF. Mỗi thực thể có ObjectStateEntrytrong ObjectStateManager. Mục nhập trạng thái chứa các giá trị ban đầu và thực tế để bạn có thể sử dụng các giá trị ban đầu để ghi đè các giá trị hiện tại nhưng bạn phải thực hiện thủ công cho từng thực thể. Nó sẽ không tôn trọng những thay đổi trong các thuộc tính / quan hệ điều hướng.

Cách phổ biến để "hoàn nguyên các thay đổi" là loại bỏ ngữ cảnh và tải lại các thực thể. Nếu bạn muốn tránh tải lại, bạn phải tạo bản sao của các thực thể và sửa đổi bản sao đó trong ngữ cảnh đối tượng mới. Nếu người dùng hủy thay đổi, bạn sẽ vẫn có các thực thể ban đầu.


4
@LadislavMrnka Chắc chắn Context.Refresh()là một ví dụ phản bác lại tuyên bố của bạn rằng không có hoạt động hoàn nguyên? Sử dụng Refresh()có vẻ là một cách tiếp cận tốt hơn (tức là dễ dàng nhắm mục tiêu vào các thực thể cụ thể hơn) so với việc loại bỏ bối cảnh và mất tất cả các thay đổi được theo dõi.
Rob

14
@robjb: Không. Làm mới chỉ có thể làm mới một thực thể hoặc tập hợp các thực thể mà bạn xác định theo cách thủ công nhưng chức năng làm mới chỉ ảnh hưởng đến các thuộc tính đơn giản (không phải quan hệ). Nó cũng không giải quyết vấn đề với các thực thể đã thêm hoặc bị xóa.
Ladislav Mrnka 14/02/12

153

Truy vấn ChangeTracker của DbContext cho các mục bẩn. Đặt trạng thái các mục đã xóa thành không thay đổi và thêm các mục vào tách rời. Đối với các mục đã sửa đổi, hãy sử dụng giá trị ban đầu và đặt giá trị hiện tại của mục nhập. Cuối cùng đặt trạng thái của mục nhập đã sửa đổi thành không thay đổi:

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
Cảm ơn - điều này thực sự đã giúp tôi!
Matt

5
Bạn cũng nên đặt các giá trị ban đầu cho các mục đã xóa. Có thể lần đầu tiên bạn thay đổi một mục và xóa nó sau đó.
Bas de Raad,

22
Đặt Statethành EntityState.Unchanged cũng sẽ ghi đè tất cả các giá trị với Original Valuesvì vậy không cần gọi SetValuesphương thức.
Abolfazl Hosnoddin

10
Phiên bản rõ ràng hơn của câu trả lời này: stackoverflow.com/a/22098063/2498426
Jerther 14/09/2016

1
Bạn ơi, điều này thật tuyệt vời! Chỉ có sửa đổi tôi đã thực hiện là sử dụng phiên bản chung của Entries <T> () để nó hoạt động cho các thư mục tương ứng của tôi. Điều này cho phép tôi kiểm soát nhiều hơn và tôi có thể quay lại đối với mỗi loại thực thể. Cảm ơn!
Daniel Mackay

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

Đang chuyển sang MSDN :

Tải lại thực thể từ cơ sở dữ liệu ghi đè bất kỳ giá trị thuộc tính nào bằng giá trị từ cơ sở dữ liệu. Thực thể sẽ ở trạng thái Không thay đổi sau khi gọi phương thức này.

Lưu ý rằng việc hoàn nguyên thông qua yêu cầu đến cơ sở dữ liệu có một số hạn chế:

  • lưu lượng mạng
  • Quá tải DB
  • thời gian phản hồi ứng dụng tăng lên

17

Điều này đã làm việc cho tôi:

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

Trong trường hợp itemlà cơ quan của khách hàng cần được hoàn nguyên.


12

Cách dễ dàng mà không cần theo dõi bất kỳ thay đổi nào. Nó sẽ nhanh hơn so với việc xem xét mọi thực thể.

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

Thời gian tạo một đối tượng hấp dẫn duy nhất ... là vài mili giây (50 mili giây). Vòng qua bộ sưu tập có thể nhanh hơn hoặc lâu hơn tùy thuộc vào kích thước của nó. Hiệu suất khôn ngoan O (1) hiếm khi là một vấn đề so với O (n). Ký hiệu Big O
Guish

Không theo dõi bạn - hiệu suất của việc loại bỏ và tạo lại kết nối. Tôi đã thử nghiệm nó trên dự án hiện có và nó hoàn thành nhanh hơn một chút so với Rollbackquy trình trên , điều này khiến nó trở thành lựa chọn tốt hơn nhiều nếu người ta muốn hoàn nguyên toàn bộ trạng thái cơ sở dữ liệu. Rollback có thể anh đào chọn tho.
majkinetor

'n' có nghĩa là số lượng đối tượng. Việc tạo lại kết nối mất khoảng 50 ms ... O (1) có nghĩa là nó luôn luôn như cũ 50ms+0*n= 50ms. O (n) có nghĩa là hiệu suất bị ảnh hưởng bởi số lượng đối tượng ... hiệu suất có thể là 2ms+0.5ms*n... vì vậy 96 đối tượng dưới đây sẽ nhanh hơn nhưng thời gian sẽ tăng tuyến tính với lượng dữ liệu.
Guish

Nếu bạn không định chọn anh đào thì đây là cách để bắt đầu với điều kiện bạn không lo lắng về băng thông.
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; 
    } 
} 

Nó đã làm việc cho tôi. Tuy nhiên bạn phải tải lại dữ liệu của mình từ ngữ cảnh để mang lại dữ liệu cũ. Nguồn tại đây


3

"Điều này đã làm việc cho tôi:

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

Trong trường hợp itemlà cơ quan của khách hàng cần được hoàn nguyên ".


Tôi đã thực hiện các thử nghiệm với ObjectContext.Refresh trong SQL Azure và "RefreshMode.StoreWins" kích hoạt một truy vấn đối với cơ sở dữ liệu cho từng thực thể và gây ra rò rỉ hiệu suất. Dựa trên tài liệu microsoft ():

ClientWins: Các thay đổi thuộc tính được thực hiện đối với các đối tượng trong ngữ cảnh đối tượng không được thay thế bằng các giá trị từ nguồn dữ liệu. Trong lần gọi tới SaveChanges tiếp theo, những thay đổi này sẽ được gửi đến nguồn dữ liệu.

StoreWins: Các thay đổi thuộc tính được thực hiện đối với các đối tượng trong ngữ cảnh đối tượng được thay thế bằng các giá trị từ nguồn dữ liệu.

ClientWins cũng không phải là một lý tưởng tốt, vì việc kích hoạt .SaveChanges sẽ thực hiện các thay đổi "bị loại bỏ" đối với nguồn dữ liệu.

Tôi chưa biết cách tốt nhất là gì, vì việc loại bỏ ngữ cảnh và tạo một ngữ cảnh mới sẽ gây ra ngoại lệ với thông báo: "Nhà cung cấp cơ bản không thành công khi mở" khi tôi cố gắng chạy bất kỳ truy vấn nào trên ngữ cảnh mới được tạo.

Trân trọng,

Henrique Clausing


2

Đối với tôi, phương pháp tốt hơn để làm điều đó là đặt EntityState.Unchangedtrên mọi thực thể bạn muốn hoàn tác thay đổi. Điều này đảm bảo các thay đổi được hoàn nguyên trên FK và có cú pháp rõ ràng hơn một chút.


4
Lưu ý: Các thay đổi sẽ quay trở lại nếu thực thể được thay đổi một lần nữa.
Nick Whaley

2

Tôi thấy điều này hoạt động tốt trong ngữ cảnh của tôi:

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


1
Tôi tin rằng điều này sẽ ngăn các thay đổi đối với thực thể tiếp tục tồn tại khi gọi DbContext.SaveChanges(), nhưng nó sẽ không trả các giá trị của thực thể về giá trị ban đầu. Và nếu trạng thái thực thể được sửa đổi từ một thay đổi sau đó, có thể tất cả các sửa đổi trước đó sẽ được giữ nguyên khi lưu?
Carl G

1
Kiểm tra mã liên kết này.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 Nó cho biết rằng việc đặt một thực thể thành trạng thái Unchaged sẽ khôi phục các giá trị ban đầu "dưới vỏ bọc".
Hannish

2

Đây là một ví dụ về những gì Mrnka đang nói đến. Phương thức sau ghi đè các giá trị hiện tại của một thực thể bằng các giá trị ban đầu và không gọi ra cơ sở dữ liệu. Chúng tôi thực hiện điều này bằng cách sử dụng thuộc tính OriginalValues ​​của DbEntityEntry và sử dụng phản xạ để đặt các giá trị theo cách chung chung. (Điều này hoạt động kể từ 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

Chúng tôi đang sử dụng EF 4, với bối cảnh Đối tượng Kế thừa. Không có giải pháp nào ở trên trả lời trực tiếp điều này cho tôi - mặc dù nó DID trả lời nó về lâu dài bằng cách thúc đẩy tôi đi đúng hướng.

Chúng ta không thể chỉ xử lý và xây dựng lại bối cảnh bởi vì một số đối tượng mà chúng ta có quanh quẩn trong bộ nhớ (chết tiệt đó là tải lười biếng !!) vẫn được gắn với ngữ cảnh nhưng có những đối tượng chưa được tải. Đối với những trường hợp này, chúng ta cần đưa mọi thứ trở lại giá trị ban đầu mà không cần tác động đến cơ sở dữ liệu và không làm rơi kết nối hiện có.

Dưới đây là giải pháp của chúng tôi cho cùng một vấn đề này:

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

Tôi hy vọng điều này sẽ giúp những người khác.


0

Một số ý tưởng hay ở trên, tôi đã chọn thực hiện ICloneable và sau đó là một phương pháp mở rộng đơn giản.

Tìm thấy ở đây: Làm cách nào để sao chép danh sách chung trong C #?

Được sử dụng như:

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

Bằng cách này, tôi có thể sao chép danh sách thực thể sản phẩm của mình, áp dụng chiết khấu cho từng mặt hàng và không phải lo lắng về việc hoàn nguyên bất kỳ thay đổi nào trên thực thể ban đầu. Không cần phải nói chuyện với DBContext và yêu cầu làm mới hoặc làm việc với ChangeTracker. Bạn có thể nói rằng tôi không sử dụng hết EF6 nhưng đây là một cách triển khai rất hay và đơn giản và tránh được một cú đánh DB. Tôi không thể nói liệu điều này có thành công hay không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.