Saya sedang mengerjakan proyek ASP.Net Core 2.0 menggunakan Entity Framework Core
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0"/>
Dan di salah satu metode daftar saya, saya mendapatkan kesalahan ini:
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
Ini adalah metode saya:
[HttpGet("{currentPage}/{pageSize}/")]
[HttpGet("{currentPage}/{pageSize}/{search}")]
public ListResponseVM<ClientVM> GetClients([FromRoute] int currentPage, int pageSize, string search)
{
var resp = new ListResponseVM<ClientVM>();
var items = _context.Clients
.Include(i => i.Contacts)
.Include(i => i.Addresses)
.Include("ClientObjectives.Objective")
.Include(i => i.Urls)
.Include(i => i.Users)
.Where(p => string.IsNullOrEmpty(search) || p.CompanyName.Contains(search))
.OrderBy(p => p.CompanyName)
.ToPagedList(pageSize, currentPage);
resp.NumberOfPages = items.TotalPage;
foreach (var item in items)
{
var client = _mapper.Map<ClientVM>(item);
client.Addresses = new List<AddressVM>();
foreach (var addr in item.Addresses)
{
var address = _mapper.Map<AddressVM>(addr);
address.CountryCode = addr.CountryId;
client.Addresses.Add(address);
}
client.Contacts = item.Contacts.Select(p => _mapper.Map<ContactVM>(p)).ToList();
client.Urls = item.Urls.Select(p => _mapper.Map<ClientUrlVM>(p)).ToList();
client.Objectives = item.Objectives.Select(p => _mapper.Map<ObjectiveVM>(p)).ToList();
resp.Items.Add(client);
}
return resp;
}
Saya agak tersesat terutama karena itu berfungsi ketika saya menjalankannya secara lokal, tetapi ketika saya menyebarkan ke server pementasan saya (IIS 8.5) itu membuat saya kesalahan ini dan itu berfungsi normal. Kesalahan mulai muncul setelah saya menambah panjang maksimal salah satu model saya. Saya juga memperbarui panjang maksimal Model Tampilan yang sesuai. Dan ada banyak metode daftar lain yang sangat mirip dan berfungsi.
Saya memiliki pekerjaan Hangfire yang sedang berjalan, tetapi pekerjaan ini tidak menggunakan entitas yang sama. Hanya itu yang menurut saya relevan. Ada gagasan tentang apa yang bisa menyebabkan ini?
sumber
Jawaban:
Saya tidak yakin apakah Anda menggunakan IoC dan Dependency Injection untuk menyelesaikan DbContext Anda di mana pun itu mungkin digunakan. Jika Anda melakukannya dan Anda menggunakan IoC asli dari .NET Core (atau IoC-Container lainnya) dan Anda mendapatkan kesalahan ini, pastikan untuk mendaftarkan DbContext Anda sebagai Transient. Melakukan
ATAU
dari pada
AddDbContext menambahkan konteks sebagai cakupan, yang mungkin menyebabkan masalah saat bekerja dengan beberapa utas.
Juga operasi async / Tunggulah, dapat menyebabkan perilaku ini, ketika menggunakan ekspresi lambda async.
Menambahkannya sebagai sementara juga memiliki kelemahannya. Anda tidak akan dapat membuat perubahan pada beberapa entitas di beberapa kelas yang menggunakan konteks karena setiap kelas akan mendapatkan contoh DbContext Anda sendiri.
Penjelasan sederhananya adalah, bahwa
DbContext
penerapannya tidak aman untuk thread. Anda dapat membaca lebih lanjut tentang ini di sinisumber
Task.Run(async () => context.Set...)
tanpa menunggu atau membuat konteks db terbatas tanpa menunggu hasilnya. Ini berarti konteks Anda mungkin sudah dibuang saat mengaksesnya. Jika Anda menggunakan Microsoft DI, Anda harus membuat sendiri cakupan ketergantungan di dalamnyaTask.Run
. Lihat tautan ini juga. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…Dalam beberapa kasus, kesalahan ini terjadi saat memanggil metode asinkron tanpa
await
kata kunci, yang dapat diselesaikan dengan mudah dengan menambahkanawait
sebelum pemanggilan metode. Namun, jawabannya mungkin tidak terkait dengan pertanyaan yang disebutkan tetapi dapat membantu memecahkan kesalahan yang serupa.sumber
First()
menjadiawait / FirstAsync()
bekerja.Pengecualian berarti
_context
sedang digunakan oleh dua thread pada saat yang bersamaan; baik dua utas dalam permintaan yang sama, atau dengan dua permintaan.Apakah
_context
statis yang Anda nyatakan mungkin? Seharusnya tidak.Atau apakah Anda menelepon
GetClients
beberapa kali dalam permintaan yang sama dari tempat lain dalam kode Anda?Anda mungkin sudah melakukan ini, tetapi idealnya, Anda akan menggunakan injeksi ketergantungan untuk Anda
DbContext
, yang berarti Anda akan menggunakanAddDbContext()
di Startup.cs Anda, dan konstruktor pengontrol Anda akan terlihat seperti ini:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Jika kode Anda tidak seperti ini, tunjukkan kepada kami dan mungkin kami dapat membantu lebih lanjut.
sumber
_context
objek Anda di utas lain? Seperti di dalamTask.Run()
misalnya?await
dengan metode async. Jika Anda tidak menggunakannyaawait
, Anda dapat secara tidak sengaja masuk ke multi-threading.Selesaikan masalah saya menggunakan baris kode ini di file Startup.cs saya.
Menambahkan layanan transien berarti bahwa setiap kali layanan diminta, instance baru dibuat saat Anda bekerja dengan injeksi Ketergantungan
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
sumber
Saya memiliki masalah yang sama dan ternyata pelayanan orang tua adalah singelton. Jadi konteksnya otomatis menjadi singelton juga. Meskipun telah dinyatakan sebagai Per Life Time Scoped di DI.
Menyuntikkan layanan dengan masa pakai yang berbeda ke yang lain
Jangan pernah memasukkan layanan Scoped & Transient ke dalam layanan Singleton. (Ini secara efektif mengubah layanan transient atau cakupan menjadi singleton.)
Jangan pernah memasukkan layanan Transient ke dalam layanan cakupan (Ini mengubah layanan transien menjadi cakupan.)
sumber
Saya pikir jawaban ini masih dapat membantu seseorang dan menghemat berkali-kali. Saya memecahkan masalah serupa dengan mengubah
IQueryable
keList
(atau ke array, collection ...).Sebagai contoh:
var list=_context.table1.where(...);
untuk
var list=_context.table1.where(...).ToList(); //or ToArray()...
sumber
Saya mengalami kesalahan yang sama. Itu terjadi karena saya menyebut metode yang dibangun sebagai
public async void ...
penggantipublic async Task ...
.sumber
Saya menghadapi masalah yang sama tetapi alasannya bukan dari yang tercantum di atas. Saya membuat tugas, membuat ruang lingkup di dalam tugas dan meminta wadah untuk mendapatkan layanan. Itu berfungsi dengan baik tetapi kemudian saya menggunakan layanan kedua di dalam tugas dan saya lupa juga memintanya ke ruang lingkup baru. Karena itu, layanan ke-2 menggunakan DbContext yang sudah dibuang.
Task task = Task.Run(() => { using (var scope = serviceScopeFactory.CreateScope()) { var otherOfferService = scope.ServiceProvider.GetService<IOfferService>(); // everything was ok here. then I did: productService.DoSomething(); // (from the main scope) and this failed because the db context associated to that service was already disposed. ... } }
Saya seharusnya melakukan ini:
var otherProductService = scope.ServiceProvider.GetService<IProductService>(); otherProductService.DoSomething();
sumber
Situasi saya berbeda: Saya mencoba memasukkan database dengan 30 pengguna, termasuk dalam peran tertentu, jadi saya menjalankan kode ini:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
Ini adalah fungsi Sinkronisasi. Di dalamnya saya memiliki 3 panggilan ke:
Saat saya ganti
.Result
dengan.GetAwaiter().GetResult()
, error ini hilang.sumber
Entity Framework Core tidak mendukung beberapa operasi paralel yang dijalankan pada
DbContext
instance yang sama . Ini mencakup eksekusiasync
kueri paralel dan penggunaan konkuren eksplisit apa pun dari beberapa utas. Oleh karena itu, selaluawait async
panggil segera, atau gunakanDbContext
instance terpisah untuk operasi yang dijalankan secara paralel.sumber
Saya memiliki layanan latar belakang yang melakukan tindakan untuk setiap entri dalam tabel. Masalahnya adalah, jika saya mengulang dan mengubah beberapa data semua pada contoh yang sama dari DbContext kesalahan ini terjadi.
Salah satu solusi, seperti yang disebutkan di utas ini adalah mengubah masa pakai DbContext menjadi sementara dengan mendefinisikannya seperti
tetapi karena saya melakukan perubahan di berbagai layanan dan melakukan perubahan sekaligus menggunakan
SaveChanges()
metode solusi ini tidak berfungsi dalam kasus saya.Karena kode saya berjalan di layanan, saya melakukan sesuatu seperti
using (var scope = Services.CreateScope()) { var entities = scope.ServiceProvider.GetRequiredService<IReadService>().GetEntities(); var writeService = scope.ServiceProvider.GetRequiredService<IWriteService>(); foreach (Entity entity in entities) { writeService.DoSomething(entity); } }
untuk dapat menggunakan layanan ini seperti permintaan sederhana. Jadi untuk mengatasi masalah ini saya hanya membagi satu lingkup menjadi dua, satu untuk kueri dan yang lainnya untuk operasi tulis seperti:
using (var readScope = Services.CreateScope()) using (var writeScope = Services.CreateScope()) { var entities = readScope.ServiceProvider.GetRequiredService<IReadService>().GetEntities(); var writeService = writeScope.ServiceProvider.GetRequiredService<IWriteService>(); foreach (Entity entity in entities) { writeService.DoSomething(entity); } }
Seperti itu, ada dua contoh berbeda dari DbContext yang digunakan secara efektif.
Solusi lain yang mungkin adalah memastikan, bahwa operasi baca telah dihentikan sebelum memulai iterasi. Itu tidak terlalu praktis dalam kasus saya karena mungkin ada banyak hasil yang semuanya perlu dimuat ke memori untuk operasi yang saya coba hindari dengan menggunakan Querizable di tempat pertama.
sumber
Pertama, beri suara positif (setidaknya) jawaban alsami. Itu membuat saya berada di jalan yang benar.
Namun bagi Anda yang melakukan IoC, berikut adalah penjelasan yang lebih mendalam.
Kesalahan saya (sama seperti yang lain)
Pengaturan kode saya. "Hanya dasar-dasarnya" ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
dan
public interface IMySpecialObjectDomainData{}
dan (catatan MyCoolDbContext sedang diinjeksi)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{ public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) { /* HERE IS WHERE TO SET THE BREAK POINT, HOW MANY TIMES IS THIS RUNNING??? */ this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null); } }
dan
public interface IMySpecialObjectManager{}
dan
public class MySpecialObjectManager: IMySpecialObjectManager { public const string ErrorMessageIMySpecialObjectDomainDataIsNull = "IMySpecialObjectDomainData is null"; private readonly IMySpecialObjectDomainData mySpecialObjectDomainData; public MySpecialObjectManager(IMySpecialObjectDomainData mySpecialObjectDomainData) { this.mySpecialObjectDomainData = mySpecialObjectDomainData ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectDomainDataIsNull, (Exception)null); } }
Dan akhirnya, kelas multi utas saya, dipanggil dari Aplikasi Konsol (aplikasi Antarmuka Baris Perintah)
public interface IMySpecialObjectThatSpawnsThreads{}
dan
public class MySpecialObjectThatSpawnsThreads: IMySpecialObjectThatSpawnsThreads { public const string ErrorMessageIMySpecialObjectManagerIsNull = "IMySpecialObjectManager is null"; private readonly IMySpecialObjectManager mySpecialObjectManager; public MySpecialObjectThatSpawnsThreads(IMySpecialObjectManager mySpecialObjectManager) { this.mySpecialObjectManager = mySpecialObjectManager ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectManagerIsNull, (Exception)null); } }
dan penumpukan DI. (Sekali lagi, ini untuk aplikasi konsol (antarmuka baris perintah) ... yang menunjukkan perilaku yang sedikit berbeda dari aplikasi web)
private static IServiceProvider BuildDi(IConfiguration configuration) { /* this is being called early inside my command line application ("console application") */ string defaultConnectionStringValue = string.Empty; /* get this value from configuration */ ////setup our DI IServiceCollection servColl = new ServiceCollection() ////.AddLogging(loggingBuilder => loggingBuilder.AddConsole()) /* THE BELOW TWO ARE THE ONES THAT TRIPPED ME UP. */ .AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>() .AddTransient<IMySpecialObjectManager, MySpecialObjectManager>() /* so the "ServiceLifetime.Transient" below................is what you will find most commonly on the internet search results */ # if (MY_ORACLE) .AddDbContext<ProvisioningDbContext>(options => options.UseOracle(defaultConnectionStringValue), ServiceLifetime.Transient); # endif # if (MY_SQL_SERVER) .AddDbContext<ProvisioningDbContext>(options => options.UseSqlServer(defaultConnectionStringValue), ServiceLifetime.Transient); # endif servColl.AddSingleton <IMySpecialObjectThatSpawnsThreads, MySpecialObjectThatSpawnsThreads>(); ServiceProvider servProv = servColl.BuildServiceProvider(); return servProv; }
Yang mengejutkan saya adalah (perubahan ke) sementara untuk
Catatan, saya pikir karena IMySpecialObjectManager disuntikkan ke "MySpecialObjectThatSpawnsThreads", objek yang disuntikkan itu harus Transient untuk menyelesaikan rantai.
Intinya adalah ....... bukan hanya Konteks Db (Saya) yang membutuhkan .Transient ... tetapi sebagian besar Grafik DI.
Tip Debugging:
Garis ini:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Tempatkan break point debugger Anda di sana. Jika MySpecialObjectThatSpawnsThreads Anda membuat N jumlah utas (katakanlah 10 utas misalnya) ...... dan baris itu hanya terkena sekali ... itu masalah Anda. DbContext Anda melintasi utas.
BONUS:
Saya akan menyarankan membaca ini di bawah url / artikel (lama tapi bagus) tentang perbedaan aplikasi web dan aplikasi konsol
https://mehdi.me/ambient-dbcontext-in-ef6/
Berikut adalah tajuk artikel jika tautan berubah.
Saya mengalami masalah ini dengan WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
contoh kode di bawah .. untuk membantu pencari internet di masa mendatang
namespace MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Workflows { using System; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Constants; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Glue; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.WorkflowSteps; using WorkflowCore.Interface; using WorkflowCore.Models; public class MySpecialObjectInterviewDefaultWorkflow : IWorkflow<MySpecialObjectInterviewPassThroughData> { public const string WorkFlowId = "MySpecialObjectInterviewWorkflowId"; public const int WorkFlowVersion = 1; public string Id => WorkFlowId; public int Version => WorkFlowVersion; public void Build(IWorkflowBuilder<MySpecialObjectInterviewPassThroughData> builder) { builder .StartWith(context => { Console.WriteLine("Starting workflow..."); return ExecutionResult.Next(); }) /* bunch of other Steps here that were using IMySpecialObjectManager.. here is where my DbContext was getting cross-threaded */ .Then(lastContext => { Console.WriteLine(); bool wroteConcreteMsg = false; if (null != lastContext && null != lastContext.Workflow && null != lastContext.Workflow.Data) { MySpecialObjectInterviewPassThroughData castItem = lastContext.Workflow.Data as MySpecialObjectInterviewPassThroughData; if (null != castItem) { Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete :) {0} -> {1}", castItem.PropertyOne, castItem.PropertyTwo); wroteConcreteMsg = true; } } if (!wroteConcreteMsg) { Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete (.Data did not cast)"); } return ExecutionResult.Next(); })) .OnError(WorkflowCore.Models.WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(60)); } } }
dan
ICollection<string> workFlowGeneratedIds = new List<string>(); for (int i = 0; i < 10; i++) { MySpecialObjectInterviewPassThroughData currentMySpecialObjectInterviewPassThroughData = new MySpecialObjectInterviewPassThroughData(); currentMySpecialObjectInterviewPassThroughData.MySpecialObjectInterviewPassThroughDataSurrogateKey = i; //// private readonly IWorkflowHost workflowHost; string wfid = await this.workflowHost.StartWorkflow(MySpecialObjectInterviewDefaultWorkflow.WorkFlowId, MySpecialObjectInterviewDefaultWorkflow.WorkFlowVersion, currentMySpecialObjectInterviewPassThroughData); workFlowGeneratedIds.Add(wfid); }
sumber
Saya mendapatkan pesan yang sama. Tapi itu tidak masuk akal dalam kasus saya. Masalah saya adalah saya menggunakan properti "NotMapped" secara tidak sengaja. Ini mungkin hanya berarti kesalahan sintaks Linq atau kelas model dalam beberapa kasus. Pesan kesalahan tampaknya menyesatkan. Arti asli dari pesan ini adalah Anda tidak dapat memanggil async pada dbcontext yang sama lebih dari sekali dalam permintaan yang sama.
[NotMapped] public int PostId { get; set; } public virtual Post Post { get; set; }
Anda dapat memeriksa tautan ini untuk detailnya, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
sumber
Saya mendapat masalah yang sama ketika saya mencoba menggunakan
FirstOrDefaultAsync()
metode async pada kode di bawah ini. Dan ketika saya memperbaikinyaFirstOrDefault()
- masalahnya sudah terpecahkan!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
sumber
Saya berhasil mendapatkan kesalahan itu dengan meneruskan
IQueryable
ke metode yang kemudian menggunakan 'daftar' IQuerable itu sebagai bagian dari kueri lain ke konteks yang sama.public void FirstMethod() { // This is returning an IQueryable var stockItems = _dbContext.StockItems .Where(st => st.IsSomething); SecondMethod(stockItems); } public void SecondMethod(IEnumerable<Stock> stockItems) { var grnTrans = _dbContext.InvoiceLines .Where(il => stockItems.Contains(il.StockItem)) .ToList(); }
Untuk menghentikan itu terjadi, saya menggunakan pendekatan di sini dan mewujudkan daftar itu sebelum meneruskannya ke metode kedua, dengan mengubah panggilan
SecondMethod
menjadiSecondMethod(stockItems.ToList()
sumber
Dalam kasus saya, saya menggunakan komponen template di Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
Masalahnya adalah memanggil metode (Hitung) di header komponen. Untuk mengatasi masalah saya mengubahnya seperti ini:
int total = MyList.Count();
dan nanti:
<BTable ID="Table1" TotalRows="total">
sumber
Saya tahu masalah ini telah ditanyakan dua tahun lalu, tetapi saya baru saja mengalami masalah ini dan perbaikan yang saya gunakan sangat membantu.
Jika Anda melakukan dua kueri dengan Konteks yang sama - Anda mungkin perlu menghapus
AsNoTracking
. Jika Anda menggunakan,AsNoTracking
Anda membuat pembaca data baru untuk setiap bacaan. Dua pembaca data tidak dapat membaca data yang sama.sumber
Dalam kasus saya, saya menggunakan kunci yang tidak mengizinkan penggunaan await dan tidak membuat peringatan kompiler ketika Anda tidak menunggu async.
Masalah:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
Cara mengatasinya: Tunggu asinkron di dalam kunci dengan membuatnya memblokir dengan .Wait ()
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
sumber
Kasus lain yang mungkin: jika Anda menggunakan koneksi langsung, jangan lupa untuk menutup if. Saya perlu menjalankan kueri SQL sewenang-wenang, dan membaca hasilnya. Ini adalah perbaikan cepat, saya tidak ingin menentukan kelas data, tidak mengatur koneksi SQL "normal". Jadi cukup saya menggunakan kembali koneksi database EFC sebagai
var connection = Context.Database.GetDbConnection() as SqlConnection
. Pastikan Anda meneleponconnection.Close()
sebelum melakukannyaContext.SaveChanges()
.sumber
Jika metode Anda mengembalikan sesuatu, Anda dapat mengatasi kesalahan ini dengan
.Result
mengakhiri pekerjaan dan.Wait()
jika tidak mengembalikan apa pun.sumber
Saya baru saja berhasil membuatnya bekerja lagi. Ini tidak masuk akal tetapi berhasil:
Saya akan menyelidiki lebih lanjut nanti tetapi metode yang saya panggil dengan hangfire menerima DBContext dan itulah kemungkinan penyebabnya.
sumber