Saat ini saya mendapatkan kesalahan ini:
System.Data.SqlClient.SqlException: Transaksi baru tidak diizinkan karena ada utas lain yang berjalan di sesi.
saat menjalankan kode ini:
public class ProductManager : IProductManager
{
#region Declare Models
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
#endregion
public IProduct GetProductById(Guid productId)
{
// Do a quick sync of the feeds...
SyncFeeds();
...
// get a product...
...
return product;
}
private void SyncFeeds()
{
bool found = false;
string feedSource = "AUTO";
switch (feedSource) // companyFeedDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
{
if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
{
var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
{
foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
{
if (targetProduct.alternateProductID == sourceProduct.AutoID)
{
found = true;
break;
}
}
if (!found)
{
var newProduct = new RivWorks.Model.Negotiation.Product();
newProduct.alternateProductID = sourceProduct.AutoID;
newProduct.isFromFeed = true;
newProduct.isDeleted = false;
newProduct.SKU = sourceProduct.StockNumber;
company.Product.Add(newProduct);
}
}
_dbRiv.SaveChanges(); // ### THIS BREAKS ### //
}
}
}
break;
}
}
}
Model # 1 - Model ini duduk di database di Server Dev kami. Model # 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
Model # 2 - Model ini duduk di database di Server Prod kami dan diperbarui setiap hari dengan umpan otomatis. alt teks http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
Catatan - Item yang dilingkari merah di Model # 1 adalah bidang yang saya gunakan untuk "memetakan" ke Model # 2. Harap abaikan lingkaran merah pada Model # 2: yaitu dari pertanyaan lain yang saya jawab sekarang.
Catatan: Saya masih perlu memasukkan cek isDeleted sehingga saya bisa menghapusnya dengan lembut dari DB1 jika sudah keluar dari inventaris klien kami.
Yang ingin saya lakukan, dengan kode khusus ini, adalah menghubungkan perusahaan di DB1 dengan klien di DB2, dapatkan daftar produk mereka dari DB2 dan masukkan di DB1 jika belum ada di sana. Pertama kali melalui harus menjadi tarikan penuh inventaris. Setiap kali dijalankan di sana setelah tidak ada yang terjadi kecuali inventaris baru masuk pada umpan semalam.
Jadi pertanyaan besar - bagaimana saya mengatasi kesalahan transaksi yang saya dapatkan? Apakah saya perlu menjatuhkan dan menciptakan kembali konteks saya setiap kali melalui loop (tidak masuk akal bagi saya)?
sumber
Jawaban:
Setelah banyak mencabut rambut saya menemukan bahwa
foreach
loop adalah biang keladinya. Yang perlu terjadi adalah memanggil EF tetapi mengembalikannya ke dalam salah satuIList<T>
dari tipe target itu kemudian mengulang padaIList<T>
.Contoh:
sumber
SaveChanges
saat Anda masih menarik hasil dari DB. Oleh karena itu solusi lain hanya menyimpan perubahan setelah loop selesai.Seperti yang sudah Anda identifikasi, Anda tidak dapat menyimpan dari dalam
foreach
yang masih menggambar dari database melalui pembaca aktif.Memanggil
ToList()
atauToArray()
tidak apa-apa untuk set data kecil, tetapi ketika Anda memiliki ribuan baris, Anda akan menghabiskan banyak memori.Lebih baik memuat baris dalam potongan.
Dengan metode ekstensi di atas, Anda dapat menulis kueri seperti ini:
Objek yang dapat ditanyakan yang Anda panggil dengan metode ini harus dipesan. Ini karena Entity Framework hanya mendukung
IQueryable<T>.Skip(int)
permintaan yang dipesan, yang masuk akal ketika Anda mempertimbangkan bahwa beberapa permintaan untuk rentang yang berbeda mengharuskan pemesanan agar stabil. Jika pemesanan tidak penting bagi Anda, pesan saja dengan kunci utama karena itu kemungkinan memiliki indeks berkerumun.Versi ini akan melakukan query database dalam batch 100. Catatan yang
SaveChanges()
dipanggil untuk setiap entitas.Jika Anda ingin meningkatkan throughput Anda secara dramatis, Anda harus menelepon
SaveChanges()
lebih jarang. Gunakan kode seperti ini sebagai gantinya:Ini menghasilkan panggilan pembaruan basis data 100 kali lebih sedikit. Tentu saja masing-masing panggilan itu perlu waktu lebih lama untuk diselesaikan, tetapi Anda tetap keluar pada akhirnya. Jarak tempuh Anda mungkin beragam, tetapi ini adalah dunia yang lebih cepat bagi saya.
Dan itu mengatasi pengecualian yang Anda lihat.
EDIT Saya meninjau kembali pertanyaan ini setelah menjalankan SQL Profiler dan memperbarui beberapa hal untuk meningkatkan kinerja. Bagi siapa saja yang tertarik, berikut adalah beberapa contoh SQL yang menunjukkan apa yang dibuat oleh DB.
Loop pertama tidak perlu melewati apa pun, jadi lebih sederhana.
Panggilan selanjutnya harus melewati potongan hasil sebelumnya, jadi perkenalkan penggunaan
row_number
:sumber
Kami sekarang telah memposting tanggapan resmi terhadap bug yang dibuka di Connect . Solusi yang kami rekomendasikan adalah sebagai berikut:
Kesalahan ini disebabkan oleh Kerangka Entitas membuat transaksi implisit selama panggilan SaveChanges (). Cara terbaik untuk mengatasi kesalahan adalah dengan menggunakan pola yang berbeda (yaitu, tidak menyimpan saat sedang membaca) atau dengan secara eksplisit menyatakan transaksi. Berikut adalah tiga solusi yang mungkin:
sumber
Memang Anda tidak bisa menyimpan perubahan di dalam satu
foreach
lingkaran dalam C # menggunakan Kerangka Entity.context.SaveChanges()
Metode bertindak seperti komit pada sistem database biasa (RDMS).Cukup buat semua perubahan (Framework Entity mana yang akan di-cache) dan kemudian simpan semuanya sekaligus
SaveChanges()
setelah loop (di luar itu), seperti perintah komit database.Ini berfungsi jika Anda dapat menyimpan semua perubahan sekaligus.
sumber
Masukkan
context.SaveChanges()
setelahforeach
(loop) Anda.sumber
Selalu Gunakan pilihan Anda sebagai Daftar
Misalnya:
Kemudian Loop melalui Koleksi sambil menyimpan perubahan
sumber
FYI: dari buku dan beberapa baris disesuaikan karena masih berlaku:
Menggunakan metode SaveChanges () memulai suatu transaksi yang secara otomatis memutar kembali semua perubahan yang ada pada database jika pengecualian terjadi sebelum iterasi selesai; jika tidak, transaksi akan dilakukan. Anda mungkin tergoda untuk menerapkan metode setelah setiap pembaruan entitas atau penghapusan daripada setelah iterasi selesai, terutama ketika Anda memperbarui atau menghapus sejumlah besar entitas.
Jika Anda mencoba mengaktifkan SaveChanges () sebelum semua data diproses, Anda dikenai pengecualian "Transaksi baru tidak diizinkan karena ada utas lain yang berjalan dalam sesi". Pengecualian terjadi karena SQL Server tidak mengizinkan memulai transaksi baru pada koneksi yang memiliki SqlDataReader terbuka, bahkan dengan Multiple Active Record Sets (MARS) yang diaktifkan oleh string koneksi (String koneksi default EF memungkinkan MARS)
Terkadang lebih baik untuk memahami mengapa hal-hal terjadi ;-)
sumber
Membuat daftar yang dapat ditelusuri ke .ToList () dan itu akan berfungsi dengan baik.
sumber
Saya mendapatkan masalah yang sama tetapi dalam situasi yang berbeda. Saya memiliki daftar item dalam kotak daftar. Pengguna dapat mengklik item dan memilih delete tetapi saya menggunakan proc yang tersimpan untuk menghapus item karena ada banyak logika yang terlibat dalam menghapus item. Ketika saya memanggil proc yang disimpan, delete berfungsi dengan baik tetapi panggilan selanjutnya ke SaveChanges akan menyebabkan kesalahan. Solusi saya adalah memanggil proc yang disimpan di luar EF dan ini bekerja dengan baik. Untuk beberapa alasan ketika saya memanggil proc yang disimpan menggunakan cara EF dalam melakukan sesuatu, ia membiarkan sesuatu terbuka.
sumber
SELECT
pernyataan dalam prosedur tersimpan yang menghasilkan set hasil kosong dan jika set hasil itu tidak dibaca,SaveChanges
melemparkan pengecualian itu.Berikut adalah 2 opsi lain yang memungkinkan Anda mengaktifkan SaveChanges () di a untuk setiap loop.
Opsi pertama adalah menggunakan satu DBContext untuk menghasilkan objek daftar Anda untuk beralih melalui, dan kemudian membuat DBContext ke-2 untuk memanggil SaveChanges () pada. Berikut ini sebuah contoh:
Opsi ke-2 adalah untuk mendapatkan daftar objek database dari DBContext, tetapi untuk memilih hanya id. Dan kemudian beralih melalui daftar id (mungkin sebuah int) dan dapatkan objek yang sesuai dengan masing-masing int, dan panggil SaveChanges () dengan cara itu. Gagasan di balik metode ini adalah mengambil daftar besar bilangan bulat, jauh lebih efisien daripada mendapatkan daftar besar objek db dan memanggil .ToList () pada seluruh objek. Ini adalah contoh dari metode ini:
sumber
Jika Anda mendapatkan kesalahan ini karena foreach dan Anda benar-benar perlu menyimpan satu entitas pertama di dalam loop dan menggunakan identitas yang dihasilkan lebih lanjut dalam loop, seperti dalam kasus saya, solusi termudah adalah menggunakan DBContext lain untuk memasukkan entitas yang akan mengembalikan Id dan menggunakan ID ini dalam konteks luar
Sebagai contoh
sumber
Jadi dalam proyek itu saya punya masalah yang sama persis masalah ini tidak dalam
foreach
atau.toList()
itu sebenarnya dalam konfigurasi AutoFac yang kami gunakan. Ini menciptakan beberapa situasi aneh dimana kesalahan di atas dilemparkan tetapi juga banyak kesalahan setara lainnya dilemparkan.Ini adalah perbaikan kami: Mengubah ini:
Untuk:
sumber
Saya tahu ini adalah pertanyaan lama tetapi saya menghadapi kesalahan ini hari ini.
dan saya menemukan bahwa, kesalahan ini dapat dilemparkan ketika pemicu tabel database mendapat kesalahan.
untuk informasi Anda, Anda dapat memeriksa pemicu tabel Anda juga ketika Anda mendapatkan kesalahan ini.
sumber
Saya perlu membaca ResultSet besar dan memperbarui beberapa catatan di tabel. Saya mencoba untuk menggunakan potongan seperti yang disarankan di Drew Noakes 's jawabannya .
Sayangnya setelah 50000 catatan saya dapat OutofMemoryException. Kerangka jawaban Entity kumpulan data besar, dari pengecualian memori menjelaskan, itu
Rekomendasi ini adalah untuk menciptakan kembali konteks Anda untuk setiap batch.
Jadi saya telah mengambil nilai Minimal dan Maksimum dari kunci utama - tabel memiliki kunci utama sebagai bilangan bulat tambahan otomatis. Kemudian saya mengambil potongan database dari catatan dengan membuka konteks untuk setiap potongan. Setelah memproses konteks chunk menutup dan melepaskan memori. Ini memastikan bahwa penggunaan memori tidak bertambah.
Di bawah ini cuplikan dari kode saya:
FromToRange adalah struktur sederhana dengan properti From and To.
sumber
Kami mulai melihat kesalahan ini "Transaksi baru tidak diizinkan karena ada utas lain yang berjalan di sesi" setelah bermigrasi dari EF5 ke EF6.
Google membawa kami ke sini tetapi kami tidak menelepon
SaveChanges()
di dalam loop. Kesalahan dimunculkan ketika menjalankan prosedur tersimpan menggunakan ObjectContext.ExecuteFunction di dalam pembacaan foreach loop dari DB.Setiap panggilan ke ObjectContext.ExecuteFunction membungkus fungsi dalam transaksi. Memulai transaksi saat sudah ada pembaca terbuka menyebabkan kesalahan.
Dimungkinkan untuk menonaktifkan pembungkus SP dalam transaksi dengan mengatur opsi berikut.
The
EnsureTransactionsForFunctionsAndCommands
pilihan memungkinkan SP untuk berjalan tanpa menciptakan transaksi sendiri dan kesalahan tidak lagi mengangkat.DbContextConfiguration.Yakinkan Transaksi untukFungsi dan Properti Perintah
sumber
Saya juga menghadapi masalah yang sama.
Inilah penyebab dan solusinya.
http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx
Pastikan sebelum menjalankan perintah manipulasi data seperti sisipan, pembaruan, Anda telah menutup semua pembaca SQL aktif sebelumnya.
Kesalahan paling umum adalah fungsi yang membaca data dari db dan mengembalikan nilai. Untuk misalnya fungsi seperti isRecordExist.
Dalam hal ini kami segera kembali dari fungsi jika kami menemukan catatan dan lupa untuk menutup pembaca.
sumber
Kode di bawah ini berfungsi untuk saya:
sumber
Dalam kasus saya, masalah muncul ketika saya memanggil Stored Procedure via EF dan kemudian SaveChanges membuang pengecualian ini. Masalahnya dalam memanggil prosedur, pencacah tidak dibuang. Saya memperbaiki kode dengan cara berikut:
sumber
Saya jauh terlambat ke pesta tetapi hari ini saya menghadapi kesalahan yang sama dan bagaimana saya menyelesaikannya sederhana. Skenario saya mirip dengan kode yang diberikan ini, saya melakukan transaksi DB di dalam nested untuk-setiap loop.
Masalahnya adalah sebagai transaksi DB Tunggal membutuhkan waktu sedikit lebih lama daripada untuk-setiap loop sehingga setelah transaksi sebelumnya tidak selesai maka traksi baru melempar pengecualian, jadi solusinya adalah membuat objek baru di untuk-setiap loop di mana Anda melakukan transaksi db.
Untuk skenario yang disebutkan di atas solusinya akan seperti ini:
sumber
Saya sedikit terlambat, tetapi saya juga memiliki kesalahan ini. Saya memecahkan masalah dengan memeriksa apa nilai-nilai itu di mana memperbarui.
Saya menemukan bahwa permintaan saya salah dan ada lebih dari 250 suntingan yang tertunda. Jadi saya mengoreksi permintaan saya, dan sekarang berfungsi dengan benar.
Semoga ini bisa membantu menyelesaikan masalah di masa depan.
sumber