Urungkan perubahan dalam entitas kerangka kerja

116

ini mungkin pertanyaan yang sepele, tetapi: Karena kerangka kerja entitas ADO.NET secara otomatis melacak perubahan (dalam entitas yang dihasilkan) dan oleh karena itu mempertahankan nilai asli, bagaimana saya dapat mengembalikan perubahan yang dibuat pada objek entitas?

Saya memiliki formulir yang memungkinkan pengguna untuk mengedit sekumpulan entitas "Pelanggan" dalam tampilan kisi.

Sekarang saya memiliki dua tombol "Terima" dan "Kembalikan": jika "Terima" diklik, saya memanggil Context.SaveChanges()dan objek yang diubah ditulis kembali ke database. Jika "Kembalikan" diklik, saya ingin semua objek mendapatkan nilai properti aslinya. Apa kode untuk itu?

Terima kasih

MartinStettner
sumber

Jawaban:

69

Tidak ada operasi pengembalian atau pembatalan perubahan di EF. Setiap entitas memiliki ObjectStateEntrydi ObjectStateManager. Entri negara berisi nilai asli dan aktual sehingga Anda dapat menggunakan nilai asli untuk menimpa nilai saat ini tetapi Anda harus melakukannya secara manual untuk setiap entitas. Ini tidak akan mengembalikan perubahan dalam properti / hubungan navigasi.

Cara umum untuk "mengembalikan perubahan" adalah membuang konteks dan memuat ulang entitas. Jika Anda ingin menghindari reload, Anda harus membuat klon entitas dan memodifikasi klon tersebut dalam konteks objek baru. Jika pengguna membatalkan perubahan, Anda masih memiliki entitas asli.

Ladislav Mrnka
sumber
4
@LadislavMrnka Tentunya Context.Refresh()adalah contoh tandingan untuk klaim Anda bahwa tidak ada operasi pengembalian? Menggunakan Refresh()tampaknya pendekatan yang lebih baik (yaitu lebih mudah ditargetkan pada entitas tertentu) daripada membuang konteks dan kehilangan semua perubahan yang terlacak.
Rob
14
@robjb: Tidak. Refresh hanya dapat menyegarkan entitas tunggal atau kumpulan entitas yang Anda tentukan secara manual tetapi fungsionalitas penyegaran hanya memengaruhi properti sederhana (bukan relasi). Itu juga tidak menyelesaikan masalah dengan entitas yang ditambahkan atau dihapus.
Ladislav Mrnka
153

Query ChangeTracker dari DbContext untuk item kotor. Setel status item yang dihapus menjadi tidak berubah dan tambahkan item ke terlepas. Untuk item yang dimodifikasi, gunakan nilai asli dan setel nilai entri saat ini. Terakhir, setel status entri yang diubah menjadi tidak berubah:

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;
        }
    }
 }
Taher Rahgooy
sumber
3
Terima kasih - ini sangat membantu saya!
Matt
5
Anda mungkin harus mengatur nilai asli ke entri yang dihapus juga. Mungkin Anda pertama kali mengubah item dan menghapusnya setelah itu.
Bas de Raad
22
Menetapkan Stateke EntityState.Unchanged juga akan menimpa semua nilai Original Valuessehingga tidak perlu memanggil SetValuesmetode.
Abolfazl Hosnoddin
10
Versi lebih bersih dari jawaban ini: stackoverflow.com/a/22098063/2498426
Jerther
1
Sobat, ini luar biasa! Hanya modifikasi yang saya buat adalah dengan menggunakan versi generik Entries <T> () sehingga berfungsi untuk respositories saya. Ini memberi saya lebih banyak kontrol dan saya dapat memutar kembali per jenis entitas. Terima kasih!
Daniel Mackay
33
dbContext.Entry(entity).Reload();

Mengakui MSDN :

Reload entitas dari database menimpa nilai properti dengan nilai dari database. Entitas akan berada dalam status Tidak Berubah setelah memanggil metode ini.

Perhatikan bahwa mengembalikan melalui permintaan ke database memiliki beberapa kekurangan:

  • lalu lintas jaringan
  • Kelebihan DB
  • peningkatan waktu respons aplikasi
Lu55
sumber
17

Ini berhasil untuk saya:

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

Di mana itementitas pelanggan akan dikembalikan.

Matyas
sumber
12

Cara mudah tanpa melacak perubahan apa pun. Ini harus lebih cepat daripada melihat setiap entitas.

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}
Guish
sumber
Waktu membuat satu objek entitas ... yaitu beberapa ms (50 ms). Pengulangan melalui koleksi mungkin lebih cepat atau lebih lama tergantung ukurannya. Dari segi performa, O (1) jarang menjadi masalah dibandingkan dengan O (n). Notasi Big O
Guish
Tidak mengikuti Anda - kinerja membuang dan menciptakan kembali koneksi. Saya mengujinya pada proyek yang ada dan selesai agak lebih cepat dari Rollbackprosedur di atas , yang menjadikannya pilihan yang jauh lebih baik jika seseorang ingin mengembalikan seluruh status database. Rollback bisa memilih ceri.
majkinetor
'n' berarti jumlah objek. Menciptakan koneksi membutuhkan waktu sekitar 50 ms ... O (1) artinya selalu dalam waktu yang sama 50ms+0*n= 50ms. O (n) artinya performansi dipengaruhi oleh jumlah objek ... performanya mungkin 2ms+0.5ms*n... jadi di bawah 96 objek akan lebih cepat tetapi waktu akan meningkat secara linier dengan jumlah data.
Guish
Jika Anda tidak akan memilih apa yang (n't) digulung kembali, ini adalah cara yang harus dilakukan asalkan Anda tidak khawatir tentang bandwidth.
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; 
    } 
} 

Itu berhasil untuk saya. Namun Anda harus memuat ulang data Anda dari konteks untuk membawa data lama. Sumber di sini

Alejandro del Río
sumber
3

"Ini berhasil untuk saya:

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

Di mana itementitas pelanggan akan dikembalikan. "


Saya telah melakukan pengujian dengan ObjectContext.Refresh di SQL Azure, dan "RefreshMode.StoreWins" menjalankan kueri terhadap database untuk setiap entitas dan menyebabkan kebocoran kinerja. Berdasarkan dokumentasi microsoft ():

ClientWins: Perubahan properti yang dibuat ke objek dalam konteks objek tidak diganti dengan nilai dari sumber data. Pada panggilan berikutnya ke SaveChanges, perubahan ini dikirim ke sumber data.

StoreWins: Perubahan properti yang dibuat pada objek dalam konteks objek diganti dengan nilai dari sumber data.

ClientWins juga bukan ide yang bagus, karena mengaktifkan .SaveChanges akan menjalankan perubahan yang "dibuang" ke sumber data.

Saya belum tahu cara terbaik apa, karena membuang konteks dan membuat yang baru menyebabkan pengecualian dengan pesan: "Penyedia yang mendasari gagal saat dibuka" ketika saya mencoba menjalankan kueri apa pun pada konteks baru yang dibuat.

salam,

Henrique Clausing

Henrique Clausing Cunha
sumber
2

Bagi saya, metode yang lebih baik untuk melakukannya adalah mengatur EntityState.Unchangedsetiap entitas yang ingin Anda batalkan perubahannya. Ini memastikan perubahan dikembalikan pada FK dan memiliki sintaks yang sedikit lebih jelas.

Naz
sumber
4
Catatan: Perubahan akan kembali jika entitas diubah lagi.
Nick Whaley
2

Saya menemukan ini berfungsi dengan baik dalam konteks saya:

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

Alex Pop
sumber
1
Saya yakin ini akan mencegah perubahan pada entitas agar tidak bertahan saat dipanggil DbContext.SaveChanges(), tetapi tidak akan mengembalikan nilai entitas ke nilai aslinya. Dan jika status entitas menjadi dimodifikasi dari perubahan selanjutnya, mungkin semua modifikasi sebelumnya akan dipertahankan setelah disimpan?
Carl G
1
Periksa kode tautan ini.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 Dikatakan bahwa pengaturan entitas ke status Unchaged memulihkan nilai asli "di bawah selimut".
Hannish
2

Ini adalah contoh dari apa yang Mrnka bicarakan. Metode berikut menimpa nilai entitas saat ini dengan nilai aslinya dan tidak memanggil database. Kami melakukan ini dengan menggunakan properti OriginalValues ​​dari DbEntityEntry, dan menggunakan refleksi untuk menyetel nilai dengan cara yang umum. (Ini berfungsi pada 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]); 
        }
    }
}
BizarroDavid
sumber
1

Kami menggunakan EF 4, dengan konteks Objek Legacy. Tak satu pun dari solusi di atas secara langsung menjawab ini untuk saya - meskipun itu DID menjawabnya dalam jangka panjang dengan mendorong saya ke arah yang benar.

Kita tidak bisa begitu saja membuang dan membangun kembali konteks karena beberapa objek yang kita miliki di dalam memori (sialan itu pemuatan lambat !!) masih melekat pada konteks tetapi memiliki turunan yang belum dimuat. Untuk kasus ini, kita perlu mengembalikan semuanya ke nilai asli tanpa memalu database dan tanpa memutuskan koneksi yang ada.

Di bawah ini adalah solusi kami untuk masalah yang sama ini:

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

Saya harap ini membantu orang lain.

Jerry
sumber
0

Beberapa ide bagus di atas, saya memilih untuk menerapkan ICloneable dan kemudian metode ekstensi sederhana.

Ditemukan di sini: Bagaimana cara mengkloning daftar generik di C #?

Untuk digunakan sebagai:

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

Dengan cara ini saya dapat mengkloning daftar entitas produk saya, menerapkan diskon ke setiap item dan tidak perlu khawatir tentang mengembalikan perubahan apa pun pada entitas asli. Tidak perlu berbicara dengan DBContext dan meminta penyegaran atau bekerja dengan ChangeTracker. Anda mungkin mengatakan saya tidak menggunakan EF6 sepenuhnya tetapi ini adalah implementasi yang sangat bagus dan sederhana dan menghindari serangan DB. Saya tidak bisa mengatakan apakah ini memiliki kinerja yang sukses atau tidak.

IbrarMumtaz
sumber