Menggunakan Transaksi atau SaveChanges (false) dan AcceptAllChanges ()?

346

Saya telah menyelidiki transaksi dan tampaknya bahwa mereka mengurus diri sendiri di EF selama saya lulus falseuntuk SaveChanges()dan kemudian memanggil AcceptAllChanges()jika ada kesalahan:

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

Bagaimana jika ada yang tidak beres? tidakkah saya harus mengembalikan atau, begitu metode saya keluar dari ruang lingkup, apakah transaksi berakhir?

Apa yang terjadi pada kolom indenty yang ditugaskan setengah jalan melalui transaksi? Saya kira jika orang lain menambahkan catatan setelah saya sebelum saya menjadi buruk maka ini berarti akan ada nilai Identitas yang hilang.

Apakah ada alasan untuk menggunakan TransactionScopekelas standar dalam kode saya?

tandai pandai besi
sumber
1
Ini membantu saya memahami mengapa SaveChanges(fase); ... AcceptAllChanges();ada pola pada awalnya. Perhatikan bagaimana jawaban yang diterima untuk pertanyaan di atas, ditulis oleh penulis blog - dan blog itu direferensikan dalam pertanyaan lain. Semuanya datang bersamaan.
The Red Pea

Jawaban:

451

Dengan Kerangka Entitas sebagian besar waktu SaveChanges()sudah cukup. Ini menciptakan transaksi, atau mendaftar dalam setiap transaksi ambient, dan melakukan semua pekerjaan yang diperlukan dalam transaksi itu.

Kadang-kadang meskipun SaveChanges(false) + AcceptAllChanges()berpasangan berguna.

Tempat yang paling berguna untuk ini adalah dalam situasi di mana Anda ingin melakukan transaksi terdistribusi di dua Konteks yang berbeda.

Yaitu sesuatu seperti ini (buruk):

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

Jika context1.SaveChanges()berhasil tetapi context2.SaveChanges()gagal seluruh transaksi yang didistribusikan dibatalkan. Namun sayangnya, Entity Framework telah membuang perubahan context1, sehingga Anda tidak dapat memutar ulang atau mencatat kegagalan secara efektif.

Tetapi jika Anda mengubah kode Anda menjadi seperti ini:

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

}

Sementara panggilan untuk SaveChanges(false)mengirim perintah yang diperlukan ke database, konteksnya sendiri tidak berubah, jadi Anda bisa melakukannya lagi jika perlu, atau Anda bisa menginterogasi ObjectStateManagerjika Anda mau.

Ini berarti jika transaksi benar-benar melempar pengecualian yang dapat Anda kompensasi, dengan mencoba kembali atau mencatat status dari setiap konteks di ObjectStateManagersuatu tempat.

Lihat posting blog saya untuk lebih lanjut.

Alex James
sumber
3
Hebat, terima kasih ... Jadi, jika sesuatu gagal, saya tidak perlu mengembalikannya ?? SaveChanges, menandainya untuk disimpan, tetapi tidak benar-benar melakukan sampai saya menerima semua perubahan .. tetapi jika terjadi kesalahan .. saya akan perlu mengembalikan tidakkah saya sehingga objek saya kembali ke keadaan yang benar?
tandai smith
33
@ Mark: jika dengan "memutar kembali" yang Anda maksud, kembalikan objek Anda ke status bahwa mereka ada di dalam basis data, maka tidak, Anda tidak ingin melakukan itu karena Anda akan kehilangan semua perubahan pengguna pada objek . SaveChanges(false)melakukan pembaruan aktual ke basis data, sambil AcceptAllChanges()memberi tahu EF, "Oke, Anda bisa melupakan hal-hal yang perlu disimpan, karena semuanya berhasil disimpan." Jika SaveChanges(false)gagal, AcceptAllChanges()tidak akan pernah dipanggil dan EF akan tetap menganggap objek Anda memiliki properti yang diubah dan perlu disimpan kembali ke database.
BlueRaja - Danny Pflughoeft
Bisakah Anda memberi saran bagaimana melakukan ini menggunakan Code First? Tidak ada parameter untuk metode SaveChanges atau AcceptAllChanges
Kirsten Greed
2
Saya telah mengajukan pertanyaan tentang penggunaan teknik ini dengan Code First di sini
Kirsten Greed
13
Ini tidak lagi mungkin di EF 6.1. Apakah Anda tahu penyesuaian seperti apa yang harus dilakukan agar bisa bekerja sekarang?
Alex Dresko
113

Jika Anda menggunakan EF6 (Entity Framework 6+), ini telah berubah untuk panggilan basis data ke SQL.
Lihat: http://msdn.microsoft.com/en-us/data/dn456843.aspx

gunakan context.Database.BeginTransaction.

Dari 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
        } 
    } 
} 
pengguna3885816
sumber
51
try-catch dengan roolback tidak diperlukan saat Anda menggunakan "menggunakan" pada transaksi.
Robert
12
Saya mengambil pengecualian untuk menjebak pengecualian seperti ini. Itu menyebabkan operasi database gagal diam-diam. Karena sifat SO, seseorang mungkin mengambil contoh ini dan menggunakannya dalam aplikasi produksi.
B2K
3
@ B2K: Poin bagus, tetapi kode ini disalin dari artikel Microsoft yang ditautkan . Saya harap tidak ada yang menggunakan kode mereka dalam produksi :)
J Bryan Price
6
@ Robert Menurut artikel MSDN, Rollback () diperlukan. Mereka sengaja meninggalkan perintah Rollback untuk contoh TransactionScope. @ B2K Saya telah menambahkan di dalam throw;potongan MSDN dan menunjukkan dengan jelas bahwa itu bukan asli dari artikel MSDN.
Todd
6
(Jika benar) Ini mungkin jelas: Kedengarannya seperti EF + MSSQL tidak perlu Rollback, tetapi EF + penyedia SQL lainnya mungkin. Karena EF seharusnya agnostik dari basis data mana ia berbicara, Rollback()disebut kalau-kalau itu berbicara dengan MySql atau sesuatu yang tidak memiliki perilaku otomatis itu.
Words Like Jared
-5

Karena beberapa basis data dapat melempar pengecualian di dbContextTransaction.Commit () jadi ini lebih baik:

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(); 
    } 
  } 
} 
eMeL
sumber
7
Saya mengambil pengecualian untuk menjebak pengecualian seperti ini. Itu menyebabkan operasi database gagal diam-diam. Karena sifat SO, seseorang mungkin mengambil contoh ini dan menggunakannya dalam aplikasi produksi.
B2K
6
Bukankah ini pada dasarnya sama dengan jawaban lain yang memberikan atribusi ke halaman MSDN yang dikutipnya? Satu-satunya perbedaan yang saya lihat adalah bahwa Anda falsemasuk context.SaveChanges();, dan juga menelepon context.AcceptAllChanges();.
Wai Ha Lee
@ B2K rollback tidak diperlukan - jika transaksi tidak berhasil, tidak ada yang berkomitmen. Juga panggilan eksplisit ke Rollback dapat gagal - lihat jawaban saya di sini stackoverflow.com/questions/41385740/…
Ken
Kembalikan bukan apa yang saya keberatankan. Penulis jawaban ini memperbarui kode mereka untuk memikirkan kembali pengecualian, sehingga menyelesaikan apa yang saya keberatankan.
B2K
Maaf, saya berkomentar dari ponsel saya. Todd kembali melempar pengecualian, eMeL tidak. Harus ada sesuatu dalam tangkapan yang memberitahukan pengembang atau pengguna masalah yang menyebabkan kemunduran. Itu bisa berupa menulis ke file log, memikirkan kembali pengecualian atau mengembalikan pesan kepada pengguna.
B2K