Kerangka Entity 5 Memperbarui Catatan

870

Saya telah menjelajahi berbagai metode pengeditan / memperbarui catatan dalam Entity Framework 5 di lingkungan ASP.NET MVC3, tetapi sejauh ini tidak ada yang mencentang semua kotak yang saya butuhkan. Saya akan menjelaskan alasannya.

Saya telah menemukan tiga metode yang akan saya sebutkan pro dan kontra:

Metode 1 - Memuat catatan asli, perbarui setiap properti

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

Pro

  • Dapat menentukan properti mana yang berubah
  • Tampilan tidak perlu mengandung setiap properti

Cons

  • 2 x permintaan pada basis data untuk memuat yang asli lalu memperbaruinya

Metode 2 - Memuat catatan asli, atur nilai yang diubah

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

Pro

  • Hanya properti yang dimodifikasi yang dikirim ke database

Cons

  • Tampilan harus mengandung setiap properti
  • 2 x permintaan pada basis data untuk memuat yang asli lalu memperbaruinya

Metode 3 - Lampirkan catatan yang diperbarui dan setel status ke EntityState.Modified

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

Pro

  • 1 x permintaan pada basis data untuk memperbarui

Cons

  • Tidak dapat menentukan properti mana yang berubah
  • Tampilan harus mengandung setiap properti

Pertanyaan

Pertanyaan saya untuk kalian Adakah cara yang bersih untuk mencapai serangkaian tujuan ini?

  • Dapat menentukan properti mana yang berubah
  • Tampilan tidak perlu mengandung setiap properti (seperti kata sandi!)
  • 1 x permintaan pada basis data untuk memperbarui

Saya mengerti ini hal yang cukup kecil untuk ditunjukkan tetapi saya mungkin kehilangan solusi sederhana untuk ini. Jika tidak metode satu akan menang ;-)

Stokedout
sumber
13
Gunakan ViewModels dan mesin pemetaan yang baik? Anda hanya mendapatkan "properti untuk diperbarui" untuk mengisi tampilan Anda (dan kemudian memperbarui). Masih akan ada 2 pertanyaan untuk memperbarui (dapatkan yang asli + perbarui itu), tapi saya tidak akan menyebut ini "Con". Jika itu satu-satunya masalah kinerja Anda, Anda pria yang bahagia;)
Raphaël Althaus
Terima kasih @ RaphaëlAlthaus, poin yang sangat valid. Saya bisa melakukan ini, tetapi saya harus membuat operasi CRUD untuk sejumlah tabel jadi saya mencari metode yang dapat bekerja dengan model secara langsung untuk menyelamatkan saya membuat n-1 ViewModel untuk setiap Model.
Stokedout
3
Nah, dalam proyek saya saat ini (banyak entitas juga) kami mulai dengan mengerjakan Model, berpikir kami akan kehilangan waktu bekerja dengan ViewModels. Kita sekarang akan ke ViewModels, dan dengan (tidak diabaikan) pekerjaan infrastruktur di awal, itu jauh, jauh, jauh lebih jelas dan lebih mudah untuk dipertahankan sekarang. Dan lebih aman (tidak perlu takut tentang "ladang tersembunyi" berbahaya atau hal-hal seperti itu)
Raphaël Althaus
1
Dan tidak ada lagi ViewBag yang mengerikan untuk mengisi DropDownLists Anda (kami memiliki setidaknya satu DropDownList di hampir semua tampilan CRU (D) kami ...)
Raphaël Althaus
Saya pikir Anda benar, salah saya karena mencoba mengabaikan ViewModels. Ya, ViewBag terkadang terlihat sedikit kotor. Saya biasanya melangkah selangkah lebih maju sesuai blog Dino Esposito dan membuat InputModels juga, sabuk dan kawat gigi tad tetapi berfungsi dengan baik. Berarti hanya 2 model tambahan per model - doh ;-)
Stokedout

Jawaban:

681

Anda mencari:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
Ladislav Mrnka
sumber
59
hai @Ladislav Mrnka, jika saya ingin memperbarui semua properti sekaligus, dapatkah saya menggunakan kode di bawah ini? db.Departments.Attach (departemen); db.Entry (departemen) .State = EntityState.Modified; db.SaveChanges ();
Foyzul Karim
23
@Foysal: Ya Anda bisa.
Ladislav Mrnka
5
Salah satu masalah dengan pendekatan ini adalah Anda tidak dapat mengejek db.Entry (), yang merupakan PITA serius. EF memiliki cerita mengejek yang cukup bagus di tempat lain - cukup menjengkelkan karena (sejauh yang saya tahu) mereka tidak punya cerita di sini.
Ken Smith
23
@Foysal Melakukan konteks. Coba (entitas). Negara = EntityState.Modified sendiri sudah cukup tidak perlu melakukan melampirkan. Ini akan secara otomatis dilampirkan sebagai modifikasinya ...
HelloWorld
4
@ Sandman4, itu berarti setiap properti lainnya harus ada di sana dan disetel ke nilai saat ini. Dalam beberapa desain aplikasi, ini tidak layak.
Dan Esparza
176

Saya sangat suka jawaban yang diterima. Saya percaya ada cara lain untuk mendekati ini juga. Katakanlah Anda memiliki daftar properti yang sangat pendek yang tidak ingin Anda sertakan dalam Tampilan, jadi saat memperbarui entitas, properti itu akan dihilangkan. Katakanlah kedua bidang tersebut adalah Kata Sandi dan SSN.

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

Contoh ini memungkinkan Anda untuk secara esensial meninggalkan logika bisnis Anda sendiri setelah menambahkan bidang baru ke tabel Pengguna Anda dan ke Tampilan Anda.

smd
sumber
Tetap saya akan menerima kesalahan jika saya tidak menentukan nilai untuk properti SSN, meskipun saya mengatur IsModified ke false, masih memvalidasi properti terhadap aturan model. Jadi jika properti ditandai sebagai NOT NULL itu akan gagal jika saya tidak menetapkan nilai berbeda dari nol.
RolandoCC
Anda tidak akan menerima kesalahan karena bidang-bidang itu tidak akan ada di formulir Anda. Anda meninggalkan bidang yang pasti tidak akan Anda perbarui, ambil entri dari database menggunakan formulir yang dikembalikan dengan melampirkannya, dan beri tahu entri bahwa bidang tersebut tidak sedang dimodifikasi. Validasi model dikontrol dalam ModelState, bukan dalam konteks. Contoh ini mereferensikan pengguna yang sudah ada, karenanya "updatedUser". Jika SSN Anda adalah bidang wajib, itu akan ada di sana ketika pertama kali dibuat.
smd
4
Jika saya mengerti dengan benar, "updatedUser" adalah turunan dari objek yang sudah diisi dengan FirstOrDefault () atau serupa, jadi saya memperbarui hanya properti yang saya ubah dan mengatur yang lain ke ISModified = false. Ini berfungsi dengan baik. Tapi, apa yang saya coba lakukan adalah memperbarui objek tanpa mengisinya terlebih dahulu, tanpa membuat FirstOrDefault () dari pembaruan. Ini adalah ketika saya menerima kesalahan jika saya tidak menentukan nilai untuk semua bidang yang diminta, bahkan jika saya menetapkan ISModified = false pada properti tersebut. entry.Property (e => e.columnA) .IsModified = false; Tanpa baris ini, ColumnA akan gagal.
RolandoCC
Apa yang Anda gambarkan adalah membuat entitas baru. Ini hanya berlaku untuk pembaruan.
smd
1
RolandoCC, masukkan db.Configuration.ValidateOnSaveEnabled = false; sebelum db. Simpan Perubahan ();
Wilky
28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();
Stefano Camisassi
sumber
Ini sepertinya solusi yang sangat bagus - tidak ada masalah atau keributan; Anda tidak perlu menentukan properti secara manual dan memperhitungkan semua peluru OP - apakah ada alasan mengapa ini tidak memiliki lebih banyak suara?
nocarrier
Tapi tidak. Ini memiliki salah satu "kontra" terbesar, lebih dari satu hit ke database. Anda masih harus memuat yang asli dengan jawaban ini.
smd
1
@smd mengapa Anda mengatakan itu mengenai database lebih dari sekali? Saya tidak melihat itu terjadi kecuali jika menggunakan SetValues ​​() memiliki efek itu tetapi sepertinya itu tidak benar.
parlemen
@ Parlemen Saya pikir saya pasti sudah tertidur ketika saya menulis itu. Permintaan maaf. Masalah sebenarnya adalah mengganti nilai nol yang dimaksudkan. Jika pengguna yang diperbarui tidak lagi memiliki referensi ke sesuatu, itu tidak benar untuk menggantinya dengan nilai asli jika Anda bermaksud menghapusnya.
smd
22

Saya telah menambahkan metode pembaruan tambahan ke kelas dasar repositori saya yang mirip dengan metode pembaruan yang dihasilkan oleh Scaffolding. Alih-alih mengatur seluruh objek untuk "dimodifikasi", ia menetapkan serangkaian properti individu. (T adalah parameter generik kelas.)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

Dan kemudian menelepon, misalnya:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

Saya suka sekali perjalanan ke database. Mungkin lebih baik melakukan ini dengan model tampilan, untuk menghindari pengulangan set properti. Saya belum melakukan itu karena saya tidak tahu bagaimana menghindari membawa pesan validasi pada model tampilan saya, validator ke dalam proyek domain saya.

Ian Warburton
sumber
Aha ... pisahkan proyek untuk model tampilan dan proyek terpisah untuk repositori yang berfungsi dengan model tampilan.
Ian Warburton
11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}
Matthew Steven Monkan
sumber
Mengapa tidak hanya DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
nelsontruran
Ini mengontrol setbagian dari pernyataan pembaruan.
Tanveer Badar
4

Hanya untuk menambah daftar opsi. Anda juga dapat mengambil objek dari database, dan menggunakan alat pemetaan otomatis seperti Pemeta Otomatis untuk memperbarui bagian dari catatan yang ingin Anda ubah ..

Bostwick
sumber
3

Tergantung pada kasus penggunaan Anda, semua solusi di atas berlaku. Ini adalah bagaimana saya biasanya melakukannya:

Untuk kode sisi server (misalnya proses batch), saya biasanya memuat entitas dan bekerja dengan proxy dinamis. Biasanya dalam proses batch Anda perlu memuat data kapan saja pada saat layanan berjalan. Saya mencoba untuk memuat data daripada menggunakan metode find untuk menghemat waktu. Tergantung pada proses saya menggunakan kontrol konkurensi optimis atau pesimis (saya selalu menggunakan optimis kecuali untuk skenario eksekusi paralel di mana saya perlu mengunci beberapa catatan dengan pernyataan sql biasa, ini jarang terjadi). Tergantung pada kode dan skenario dampaknya dapat dikurangi menjadi hampir nol.

Untuk skenario sisi klien, Anda memiliki beberapa opsi

  1. Gunakan model tampilan. Model harus memiliki properti UpdateStatus (tidak dimodifikasi-dimasukkan-diperbarui-dihapus). Adalah tanggung jawab klien untuk menetapkan nilai yang benar ke kolom ini tergantung pada tindakan pengguna (masukkan-perbarui-hapus). Server dapat meminta db untuk nilai-nilai asli atau klien harus mengirim nilai-nilai asli ke server bersama dengan baris yang diubah. Server harus melampirkan nilai-nilai asli dan menggunakan kolom UpdateStatus untuk setiap baris untuk memutuskan bagaimana menangani nilai-nilai baru. Dalam skenario ini saya selalu menggunakan konkurensi optimis. Ini hanya akan melakukan memasukkan - memperbarui - menghapus pernyataan dan tidak memilih, tetapi mungkin perlu beberapa kode pintar untuk berjalan grafik dan memperbarui entitas (tergantung pada skenario Anda - aplikasi). Seorang mapper dapat membantu tetapi tidak menangani logika CRUD

  2. Gunakan perpustakaan seperti wind.js yang menyembunyikan sebagian besar kompleksitas ini (seperti yang dijelaskan dalam 1) dan mencoba menyesuaikannya dengan case use Anda.

Semoga ini bisa membantu

Chriss
sumber