SqlException dari Entity Framework - Transaksi baru tidak diizinkan karena ada utas lain yang berjalan di sesi

600

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)?

Keith Barrows
sumber
6
Ini adalah pertanyaan paling detail yang pernah saya lihat.
9
Adakah yang belum prosedur tersimpan?
David

Jawaban:

690

Setelah banyak mencabut rambut saya menemukan bahwa foreachloop adalah biang keladinya. Yang perlu terjadi adalah memanggil EF tetapi mengembalikannya ke dalam salah satu IList<T>dari tipe target itu kemudian mengulang pada IList<T>.

Contoh:

IList<Client> 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;
    // ...
}
Keith Barrows
sumber
14
Ya, ini juga menyebabkan saya sakit kepala. Saya hampir jatuh dari kursi ketika saya menemukan masalah! Saya memahami alasan teknis di balik masalah ini, tetapi ini tidak intuitif dan tidak membantu pengembang untuk jatuh ke dalam "lubang kesuksesan" blogs.msdn.com/brada/archive/2003/10/02/50420. aspx
Dokter Jones
9
Bukankah itu buruk untuk kinerja untuk dataset besar? Jika Anda memiliki jutaan catatan di tabel. ToList () akan menyedot semuanya ke dalam memori. Saya mengalami masalah ini dan bertanya-tanya apakah yang berikut ini akan layak a) Lepaskan entitas b) Buat ObjectContext baru dan lampirkan entitas terpisah ke dalamnya. c) Panggil SaveChanges () pada ObjectContext baru d) Lepaskan entitas dari ObjectContext baru e) Lampirkan kembali ke ObjectContext lama
Abhijeet Patel
150
Masalahnya adalah bahwa Anda tidak dapat menelepon SaveChangessaat Anda masih menarik hasil dari DB. Oleh karena itu solusi lain hanya menyimpan perubahan setelah loop selesai.
Drew Noakes
4
Setelah digigit juga, saya menambahkan ini ke Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/… Jangan ragu untuk memilihnya.
Ian Mercer
36
Pengembang kami cenderung menambahkan .ToList () ke kueri LINQ apa ​​pun tanpa memikirkan konsekuensinya. Ini harus menjadi yang pertama kali menambahkan .ToList () benar-benar bermanfaat!
Marc
267

Seperti yang sudah Anda identifikasi, Anda tidak dapat menyimpan dari dalam foreachyang masih menggambar dari database melalui pembaca aktif.

Memanggil ToList()atau ToArray()tidak apa-apa untuk set data kecil, tetapi ketika Anda memiliki ribuan baris, Anda akan menghabiskan banyak memori.

Lebih baik memuat baris dalam potongan.

public static class EntityFrameworkUtil
{
    public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
    {
        return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
    }

    public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
    {
        int chunkNumber = 0;
        while (true)
        {
            var query = (chunkNumber == 0)
                ? queryable 
                : queryable.Skip(chunkNumber * chunkSize);
            var chunk = query.Take(chunkSize).ToArray();
            if (chunk.Length == 0)
                yield break;
            yield return chunk;
            chunkNumber++;
        }
    }
}

Dengan metode ekstensi di atas, Anda dapat menulis kueri seperti ini:

foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
    // do stuff
    context.SaveChanges();
}

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:

foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
    foreach (var client in chunk)
    {
        // do stuff
    }
    context.SaveChanges();
}

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.

SELECT TOP (100)                     -- the chunk size 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC

Panggilan selanjutnya harus melewati potongan hasil sebelumnya, jadi perkenalkan penggunaan row_number:

SELECT TOP (100)                     -- the chunk size
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM (
    SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
    OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
    FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 100   -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC
Drew Noakes
sumber
17
Terima kasih. Penjelasan Anda jauh lebih berguna daripada yang ditandai sebagai "Dijawab".
Wagner da Silva
1
Ini bagus. hanya satu hal: Jika Anda bertanya pada kolom dan memperbarui nilai kolom itu, Anda harus menggunakan chunkNumber ++; . Katakanlah Anda memiliki kolom "ModifiedDate" dan Anda bertanya. Di mana (x => x.ModifiedDate! = Null), dan pada akhir foreach Anda menetapkan nilai untuk ModifiedDate. Dengan cara ini Anda tidak mengulangi setengah dari catatan karena setengah dari catatan dilewati.
Arvand
Sayangnya pada dataset besar Anda akan mendapat penjelasan OutofMemoryException -lihat dalam kumpulan data kerangka kerja Entity, di luar memori . Saya telah menjelaskan cara memperbarui konteks Anda setiap batch di SqlException dari Entity Framework - Transaksi baru tidak diizinkan karena ada utas lain yang berjalan di sesi
Michael Freidgeim
Saya pikir ini harus berhasil. var skip = 0; const int take = 100; Daftar <Employee> emps; while ((emps = db.Employees.Skip (lewati). Take (take) .ToList ()). Hitungan> 0) {skip + = take; foreach (var emp in emps) {// Lakukan hal-hal di sini}} Saya akan merumuskan jawaban ini tetapi itu akan dikubur di bawah tumpukan jawaban di bawah ini dan ini berkaitan dengan pertanyaan ini.
jwize
123

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:

// 1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
    foreach (var person in context.People)
    {
        // Change to person
    }
    context.SaveChanges();
}

// 2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
    using (var context = new MyContext())
    {
        foreach (var person in context.People)
        {
            // Change to person
            context.SaveChanges();
        }
    }
    transaction.Complete();
}

// 3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
    var people = context.People.ToList(); // Note that this forces the database
                                          // to evaluate the query immediately
                                          // and could be very bad for large tables.

    foreach (var person in people)
    {
        // Change to person
        context.SaveChanges();
    }
} 
Mark Stafford - MSFT
sumber
6
Jika Anda mengambil rute Transaksi, melempar dengan TransactionScope mungkin tidak memperbaikinya - jangan lupa untuk memperpanjang Timeout jika apa yang Anda lakukan bisa memakan waktu lama - misalnya jika Anda akan secara interaktif men-debug kode membuat Panggilan DB. Berikut kode yang memperpanjang batas waktu transaksi menjadi satu jam: using (var transaction = new TransactionScope (TransactionScopeOption.Required, TimeSpan baru (1, 0, 0)))
Chris Moschini
Saya telah menemukan kesalahan ini saat pertama kali saya menyimpang dari "jalur tutorial" menjadi contoh nyata saya sendiri! Bagi saya, bagaimanapun, solusi yang lebih sederhana, SIMPAN SETELAH ITERASI, semakin baik! (Saya pikir 99% dari kasus ini terjadi, dan hanya 1% benar-benar HARUS melakukan database simpan DI DALAM loop)
spiderman
Kotor. Saya baru saja menemukan kesalahan ini. Sangat jahat. Saran ke-2 bekerja seperti pesona bagi saya bersamaan dengan memindahkan SaveChanges saya ke loop. Saya pikir memiliki menyimpan perubahan di luar loop lebih baik untuk perubahan batching. Tapi baiklah. Saya rasa tidak?! :(
Tn. Young
Tidak bekerja untuk saya .NET 4.5. Ketika menggunakan TransactionScope saya mendapatkan kesalahan berikut "Penyedia yang mendasari gagal pada EnlistTransaction. {" Manajer transaksi mitra telah menonaktifkan dukungannya untuk transaksi jarak jauh / jaringan. (Pengecualian dari HRESULT: 0x8004D025) "}". Saya akhirnya melakukan pekerjaan di luar iterasi.
Diganta Kumar
Menggunakan TransactionScope berbahaya, karena meja dikunci untuk saat seluruh transaksi.
Michael Freidgeim
19

Memang Anda tidak bisa menyimpan perubahan di dalam satu foreachlingkaran 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.

Edgardo Pichardo C.
sumber
2
Saya pikir itu menarik untuk melihat "sistem database reguler (RDMS)" di sini
Dinerdo
1
Ini keliru, karena berulang kali memanggil SaveChanges baik-baik saja di 90% konteks di EF.
Pxtl
Tampaknya seolah-olah berulang kali memanggil SaveChanges baik-baik saja, kecuali loop foreach berulang di atas Entitas db.
kerbasaurus
1
Aha! Bawa konteks di dalam untuk-setiap loop! (pffft ... apa yang aku pikirkan? ..) Terima kasih!
Adam Cox
18

Masukkan context.SaveChanges()setelah foreach(loop) Anda.

Majid
sumber
Ini adalah opsi yang lebih baik yang saya temukan dalam kasus saya karena menyimpan di dalam foreach
Almeida
2
Ini tidak selalu merupakan opsi.
Pxtl
9

Selalu Gunakan pilihan Anda sebagai Daftar

Misalnya:

var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList();

Kemudian Loop melalui Koleksi sambil menyimpan perubahan

 foreach (var item in tempGroupOfFiles)
             {
                 var itemToUpdate = item;
                 if (itemToUpdate != null)
                 {
                     itemToUpdate.FileStatusID = 8;
                     itemToUpdate.LastModifiedDate = DateTime.Now;
                 }
                 Entities.SaveChanges();

             }
mzonerz
sumber
1
Ini bukan praktik yang baik sama sekali. Anda tidak harus menjalankan SaveChanges sesering itu jika Anda tidak perlu, dan Anda pasti tidak boleh "Selalu gunakan pilihan Anda sebagai Daftar"
Dinerdo
@ Dinerdo itu benar-benar tergantung pada skenario. Dalam kasus saya, saya memiliki 2 loop foreach. Yang luar memiliki kueri db sebagai daftar. Sebagai contoh, langkah ini melintasi perangkat perangkat keras. Foreach batin mengambil beberapa data dari setiap perangkat. Sesuai kebutuhan, saya perlu menyimpan ke database data setelah diambil dari setiap perangkat satu per satu. Ini bukan opsi untuk menyimpan semua data di akhir proses. Saya mengalami kesalahan yang sama tetapi solusi mzonerz bekerja.
jstuardo
@ jstuardo Bahkan dengan batching?
Dinerdo
@Dinerdo Saya setuju itu bukan praktik yang baik di tingkat filosofis. Namun, ada beberapa situasi di mana di dalam loop untuk kode panggilan metode lain (katakanlah metode AddToLog ()) yang mencakup panggilan ke db.SaveChanges () secara lokal. Dalam situasi ini Anda tidak dapat benar-benar mengontrol panggilan ke db. Simpan Perubahan. Dalam hal ini, menggunakan ToList () atau struct yang serupa akan berfungsi seperti yang disarankan oleh mzonerz. Terima kasih!
A. Varma
Dalam praktiknya, ini akan menyakiti Anda lebih dari itu akan membantu. Saya mendukung apa yang saya katakan - ToList () pasti tidak boleh digunakan sepanjang waktu, dan menyimpan perubahan setelah setiap item adalah sesuatu yang harus dihindari sedapat mungkin dalam aplikasi berkinerja tinggi. Ini akan menjadi temp memperbaiki IMO. Apapun metode logging yang Anda miliki, idealnya juga harus memanfaatkan buffering.
Dinerdo
8

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

Herman Van Der Blom
sumber
1
Cara yang baik untuk menghindari ini adalah ketika Anda memiliki pembaca yang terbuka untuk membuka yang kedua dan menempatkan operasi tersebut di pembaca kedua. Ini adalah sesuatu yang dapat Anda butuhkan ketika Anda memperbarui master / detail dalam kerangka entitas. Anda membuka koneksi pertama untuk catatan master dan yang kedua untuk catatan detail. jika Anda hanya membaca seharusnya tidak ada masalah. masalah terjadi saat memperbarui.
Herman Van Der Blom
Penjelasan yang bermanfaat. Anda benar, ada baiknya untuk memahami mengapa segala sesuatu terjadi.
Dov Miller
8

Membuat daftar yang dapat ditelusuri ke .ToList () dan itu akan berfungsi dengan baik.

Wojciech Seweryn
sumber
1
Harap berikan contoh alih-alih hanya memposting solusi.
Ronnie Oosting
5

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.

MikeKulls
sumber
3
Punya masalah yang sama baru-baru ini: alasan dalam kasus saya adalah SELECTpernyataan dalam prosedur tersimpan yang menghasilkan set hasil kosong dan jika set hasil itu tidak dibaca, SaveChangesmelemparkan pengecualian itu.
n0rd
Hal yang sama dengan hasil yang belum dibaca dari SP, terima kasih banyak untuk petunjuk)
Pavel K
4

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:

//Get your IQueryable list of objects from your main DBContext(db)    
IQueryable<Object> objects = db.Object.Where(whatever where clause you desire);

//Create a new DBContext outside of the foreach loop    
using (DBContext dbMod = new DBContext())
{   
    //Loop through the IQueryable       
    foreach (Object object in objects)
    {
        //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id           
        Object objectMod = dbMod.Object.Find(object.id);

        //Make whatever changes you need on objectMod
        objectMod.RightNow = DateTime.Now;

        //Invoke SaveChanges() on the dbMod context         
        dbMod.SaveChanges()
    }
}

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:

//Get the list of objects you want from your DBContext, and select just the Id's and create a list
List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList();

var objects = Ids.Select(id => db.Objects.Find(id));

foreach (var object in objects)
{
    object.RightNow = DateTime.Now;
    db.SaveChanges()
}
jjspierx
sumber
Ini adalah alternatif yang bagus yang saya pikirkan dan lakukan, tetapi ini perlu ditingkatkan. Catatan: i) Anda dapat beralih sebagai enumerable yang bagus untuk set sangat besar; ii) Anda dapat menggunakan perintah NoTracking untuk menghindari masalah dengan memuat begitu banyak catatan (jika itu skenario Anda); iii) Saya benar-benar menyukai opsi primary-key saja - itu sangat cerdas karena Anda memuat lebih sedikit data ke dalam memori, tetapi Anda tidak berurusan dengan Ambil / Lewati pada dataset mendasar yang berpotensi dinamis.
Todd
4

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

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }
Hemant Sakta
sumber
2

Jadi dalam proyek itu saya punya masalah yang sama persis masalah ini tidak dalam foreachatau .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:

container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope();
container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();

Untuk:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();
VeldMuijz
sumber
Bisakah Anda menguraikan apa yang menurut Anda masalahnya? Anda memecahkan ini dengan membuat Dbcontext baru setiap kali?
eran otzap
2

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.

nadir
sumber
2

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

EF membuat salinan data kedua yang digunakan untuk deteksi perubahan (sehingga dapat mempertahankan perubahan pada basis data). EF memegang set kedua ini untuk seumur hidup konteks dan set ini yang membuat Anda kehabisan memori.

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:

  public void ProcessContextByChunks ()
  {
        var tableName = "MyTable";
         var startTime = DateTime.Now;
        int i = 0;
         var minMaxIds = GetMinMaxIds();
        for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize)
        {
            try
            {
                using (var context = InitContext())
                {   
                    var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize));
                    try
                    {
                        foreach (var row in chunk)
                        {
                            foundCount = UpdateRowIfNeeded(++i, row);
                        }
                        context.SaveChanges();
                    }
                    catch (Exception exc)
                    {
                        LogChunkException(i, exc);
                    }
                }
            }
            catch (Exception exc)
            {
                LogChunkException(i, exc);
            }
        }
        LogSummaryLine(tableName, i, foundCount, startTime);
    }

    private FromToRange<int> GetminMaxIds()
    {
        var minMaxIds = new FromToRange<int>();
        using (var context = InitContext())
        {
            var allRows = GetMyTableQuery(context);
            minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0);  
            minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0);
        }
        return minMaxIds;
    }

    private IQueryable<MyTable> GetMyTableQuery(MyEFContext context)
    {
        return context.MyTable;
    }

    private  MyEFContext InitContext()
    {
        var context = new MyEFContext();
        context.Database.Connection.ConnectionString = _connectionString;
        //context.Database.Log = SqlLog;
        return context;
    }

FromToRange adalah struktur sederhana dengan properti From and To.

Michael Freidgeim
sumber
Saya gagal melihat bagaimana Anda "memperbarui" konteks Anda. Sepertinya Anda hanya membuat konteks baru untuk setiap chunk.
Suncat2000
@ Suncat2000, Anda benar, konteks harus menjadi objek yang pendek stackoverflow.com/questions/43474112/...
Michael Freidgeim
2

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.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

The EnsureTransactionsForFunctionsAndCommandspilihan memungkinkan SP untuk berjalan tanpa menciptakan transaksi sendiri dan kesalahan tidak lagi mengangkat.

DbContextConfiguration.Yakinkan Transaksi untukFungsi dan Properti Perintah

JamPickle
sumber
1

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.

Vinod T. Patil
sumber
7
Apa yang dimaksud "tutup pembaca" di Entity Framework? Tidak ada pembaca yang terlihat dalam kueri seperti var result = dari pelanggan di myDb.Customers di mana customer.Id == customerId pilih pelanggan; hasil pengembalian.FirstOrDefault ();
Anthony
@Anthony Seperti jawaban lain katakan, jika Anda menggunakan EF untuk menghitung lebih dari permintaan LINQ (IQueryable), DataReader yang mendasarinya akan tetap terbuka sampai baris terakhir diulangi. Tetapi meskipun MARS adalah fitur penting untuk diaktifkan dalam koneksi-string, masalah dalam OP masih belum diselesaikan dengan MARS saja. Masalahnya adalah mencoba SaveChanges saat DataReader yang mendasarinya masih terbuka.
Todd
1

Kode di bawah ini berfungsi untuk saya:

private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
    try
    {
        foreach (var product in _context.products)
        {
            product.pchecked = false;
            _context.products.Attach(product);
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }
    catch (Exception extofException)
    {
        MessageBox.Show(extofException.ToString());

    }
    productsDataGrid.Items.Refresh();
}
pengguna2918896
sumber
2
Selamat datang di SO! Pertimbangkan untuk menambahkan penjelasan dan / atau tautan yang menjelaskan mengapa ini cocok untuk Anda. Jawaban khusus kode biasanya dianggap tidak berkualitas baik untuk SO.
codeMagic
1

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:

public bool IsUserInRole(string username, string roleName, DataContext context)
{          
   var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName);

   //using here solved the issue
   using (var en = result.GetEnumerator()) 
   {
     if (!en.MoveNext())
       throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF");
     int? resultData = en.Current;

     return resultData == 1;//1 = success, see T-SQL for return codes
   }
}
Tomas Kubes
sumber
0

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:

foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                {
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
                    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 ### //
                    }
                }
Usman
sumber
0

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.

Jadi dalam situasi saya: Periksa kueri untuk kesalahan, dengan men-debug hasil yang dikembalikan kueri. Setelah itu perbaiki kueri.

Semoga ini bisa membantu menyelesaikan masalah di masa depan.

Maks
sumber