Ich arbeite an einem ASP.Net Core 2.0-Projekt mit 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"/>
Und in einer meiner Listenmethoden erhalte ich diesen Fehler:
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()
Das ist meine Methode:
[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;
}
Ich bin ein bisschen verloren, vor allem, weil es funktioniert, wenn ich es lokal ausführe, aber wenn ich es auf meinem Staging-Server (IIS 8.5) bereitstelle, erhalte ich diesen Fehler und es funktionierte normal. Der Fehler trat auf, nachdem ich die maximale Länge eines meiner Modelle erhöht hatte. Ich habe auch die maximale Länge des entsprechenden Ansichtsmodells aktualisiert. Und es gibt viele andere Listenmethoden, die sehr ähnlich sind und funktionieren.
Ich hatte einen Hangfire-Job ausgeführt, aber dieser Job verwendet nicht dieselbe Entität. Das ist alles, was ich für relevant halten kann. Irgendwelche Ideen, was dies verursachen könnte?
quelle
Antworten:
Ich bin nicht sicher, ob Sie IoC und Dependency Injection verwenden, um Ihren DbContext aufzulösen, wo immer er verwendet wird. Wenn Sie dies tun und native IoC aus .NET Core (oder einem anderen IoC-Container) verwenden und diese Fehlermeldung angezeigt wird, müssen Sie Ihren DbContext als Transient registrieren. Machen
ODER
anstatt
AddDbContext fügt den Kontext als Gültigkeitsbereich hinzu, was bei der Arbeit mit mehreren Threads zu Problemen führen kann.
Auch asynchrone / warten-Operationen können dieses Verhalten verursachen, wenn asynchrone Lambda-Ausdrücke verwendet werden.
Das Hinzufügen als vorübergehend hat auch seine Nachteile. Sie können keine Änderungen an einer Entität über mehrere Klassen vornehmen, die den Kontext verwenden, da jede Klasse eine eigene Instanz Ihres DbContext erhält.
Die einfache Erklärung dafür ist, dass die
DbContext
Implementierung nicht threadsicher ist. Mehr dazu lesen Sie hierquelle
Task.Run(async () => context.Set...)
ohne darauf zu warten, oder erstellen einen DB-Kontext mit Gültigkeitsbereich, ohne auf das Ergebnis zu warten. Dies bedeutet, dass Ihr Kontext wahrscheinlich bereits beim Zugriff darauf verfügbar ist. Wenn Sie mit Microsoft DI arbeiten, müssen Sie selbst einen Abhängigkeitsbereich erstellenTask.Run
. Schauen Sie sich auch diese Links an. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…In einigen Fällen tritt dieser Fehler auf, wenn eine asynchrone Methode ohne das
await
Schlüsselwortawait
aufgerufen wird. Dies kann einfach durch Hinzufügen vor dem Methodenaufruf behoben werden . Die Antwort bezieht sich möglicherweise nicht auf die genannte Frage, kann jedoch zur Lösung eines ähnlichen Fehlers beitragen.quelle
First()
zuawait / FirstAsync()
arbeiten.Die Ausnahme bedeutet, dass
_context
zwei Threads gleichzeitig verwendet werden. entweder zwei Threads in derselben Anforderung oder durch zwei Anforderungen.Ist Ihre
_context
deklarierte statische Aufladung vielleicht? Es sollte nicht sein.Oder rufen Sie
GetClients
in derselben Anfrage mehrmals von einer anderen Stelle in Ihrem Code aus an?Möglicherweise tun Sie dies bereits, aber im Idealfall verwenden Sie die Abhängigkeitsinjektion für Ihre
DbContext
, was bedeutet, dass Sie sieAddDbContext()
in Ihrer Startup.cs verwenden, und Ihr Controller-Konstruktor sieht ungefähr so aus:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Wenn Ihr Code nicht so ist, zeigen Sie es uns und vielleicht können wir Ihnen weiterhelfen.
quelle
_context
Objekt in anderen Threads? Wie in einemTask.Run()
zum Beispiel?await
asynchrone Methoden verwenden. Wenn Sie nicht verwendenawait
, können Sie ungewollt in Multithreading geraten.Lösen Sie mein Problem mit dieser Codezeile in meiner Datei Startup.cs.
Das Hinzufügen eines vorübergehenden Dienstes bedeutet, dass jedes Mal, wenn der Dienst angefordert wird, eine neue Instanz erstellt wird, wenn Sie mit der Abhängigkeitsinjektion arbeiten
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
quelle
Ich hatte das gleiche Problem und es stellte sich heraus, dass der Elternservice ein Singelton war. So wurde der Kontext automatisch auch zu Singelton. Obwohl in DI als Per Life Time Scoped deklariert wurde.
Injizieren von Diensten mit unterschiedlichen Lebensdauern in einen anderen
Fügen Sie niemals Scoped & Transient-Dienste in den Singleton-Dienst ein. (Dadurch wird der vorübergehende Dienst oder Dienst mit Gültigkeitsbereich effektiv in den Singleton konvertiert.)
Injizieren Sie niemals vorübergehende Dienste in den Bereichsdienst (Dies wandelt den vorübergehenden Dienst in den Bereich um.)
quelle
Ich hatte den gleichen Fehler. Es geschah, weil ich eine Methode aufgerufen habe, die als
public async void ...
statt konstruiert wurdepublic async Task ...
.quelle
Ich denke, diese Antwort kann immer noch jemandem helfen und viele Male sparen. Ich habe ein ähnliches Problem gelöst, indem ich
IQueryable
zuList
(oder zu Array, Sammlung ...) gewechselt bin .Zum Beispiel:
var list=_context.table1.where(...);
zu
var list=_context.table1.where(...).ToList(); //or ToArray()...
quelle
Ich hatte das gleiche Problem, aber der Grund war keiner der oben aufgeführten. Ich habe eine Aufgabe erstellt, einen Bereich innerhalb der Aufgabe erstellt und den Container gebeten, einen Dienst zu erhalten. Das hat gut funktioniert, aber dann habe ich einen zweiten Dienst innerhalb der Aufgabe verwendet und vergessen, ihn auch für den neuen Bereich anzufordern. Aus diesem Grund verwendete der 2. Dienst einen DbContext, der bereits entsorgt wurde.
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. ... } }
Ich hätte das tun sollen:
var otherProductService = scope.ServiceProvider.GetService<IProductService>(); otherProductService.DoSomething();
quelle
Entity Framework Core unterstützt nicht mehrere parallele Vorgänge, die auf derselben
DbContext
Instanz ausgeführt werden. Dies umfasst sowohl die parallele Ausführung vonasync
Abfragen als auch die explizite gleichzeitige Verwendung mehrerer Threads.await async
Rufen Sie daher immer sofort auf oder verwenden Sie separateDbContext
Instanzen für Operationen, die parallel ausgeführt werden.quelle
Meine Situation ist anders: Ich habe versucht, die Datenbank mit 30 Benutzern zu versehen, die zu bestimmten Rollen gehören, also habe ich diesen Code ausgeführt:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
Dies war eine Synchronisierungsfunktion. Darin hatte ich 3 Anrufe zu:
Als ich ersetzt
.Result
mit.GetAwaiter().GetResult()
, ging dieser Fehler weg.quelle
Ich habe die gleiche Nachricht erhalten. Aber in meinem Fall macht es keinen Sinn. Mein Problem ist, dass ich versehentlich eine "NotMapped" -Eigenschaft verwendet habe. Dies bedeutet wahrscheinlich nur in einigen Fällen einen Fehler der Linq-Syntax oder der Modellklasse. Die Fehlermeldung scheint irreführend. Die ursprüngliche Bedeutung dieser Nachricht ist, dass Sie in derselben Anforderung nicht mehr als einmal asynchron für denselben Datenbankkontext aufrufen können.
[NotMapped] public int PostId { get; set; } public virtual Post Post { get; set; }
Sie können diesen Link für Details überprüfen, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
quelle
Ich habe einen Hintergrunddienst, der für jeden Eintrag in einer Tabelle eine Aktion ausführt. Das Problem ist, dass dieser Fehler auftritt, wenn ich einige Daten auf derselben Instanz des DbContext durchlaufe und ändere.
Eine Lösung, wie in diesem Thread erwähnt, besteht darin, die Lebensdauer des DbContext auf vorübergehend zu ändern, indem Sie ihn wie folgt definieren
aber weil ich Änderungen an mehreren verschiedenen Diensten vornehme und sie gleichzeitig mit dem festschreibe
SaveChanges()
Methode festschreibe, funktioniert diese Lösung in meinem Fall nicht.Da mein Code in einem Dienst ausgeführt wird, habe ich so etwas getan
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); } }
in der Lage zu sein, den Dienst so zu nutzen, als wäre es eine einfache Anfrage. Um das Problem zu lösen, habe ich einfach den einzelnen Bereich in zwei Bereiche aufgeteilt, einen für die Abfrage und einen für die Schreibvorgänge wie folgt:
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); } }
Auf diese Weise werden effektiv zwei verschiedene Instanzen des DbContext verwendet.
Eine andere mögliche Lösung wäre, sicherzustellen, dass der Lesevorgang beendet ist, bevor die Iteration gestartet wird. Das ist in meinem Fall nicht sehr praktisch, da es viele Ergebnisse geben könnte, die alle für den Vorgang in den Speicher geladen werden müssten, den ich zu vermeiden versuchte, indem ich zuerst eine Abfragbare Datei verwendete.
quelle
Ich habe es geschafft, diesen Fehler zu erhalten, indem ich eine
IQueryable
an eine Methode übergeben habe, die diese IQueryable-Liste als Teil einer anderen Abfrage an denselben Kontext verwendet hat.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(); }
Um dies zu verhindern, habe ich hier den Ansatz verwendet und diese Liste materialisiert, bevor ich sie an die zweite Methode übergeben habe, indem ich den Aufruf in "
SecondMethod
Sein" geändert habeSecondMethod(stockItems.ToList()
quelle
Stimmen Sie zuerst (zumindest) alsamis Antwort ab. Das hat mich auf den richtigen Weg gebracht.
Aber für diejenigen unter Ihnen, die IoC machen, ist hier ein etwas tieferer Tauchgang.
Mein Fehler (wie bei anderen)
Mein Code-Setup. "Nur die Grundlagen" ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
und
public interface IMySpecialObjectDomainData{}
und (Hinweis MyCoolDbContext wird injiziert)
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); } }
und
public interface IMySpecialObjectManager{}
und
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); } }
Und schließlich meine Multithread-Klasse, die von einer Konsolen-App (Befehlszeilenschnittstellen-App) aufgerufen wird.
public interface IMySpecialObjectThatSpawnsThreads{}
und
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); } }
und der DI-Aufbau. (Auch dies gilt für eine Konsolenanwendung (Befehlszeilenschnittstelle) ... die sich geringfügig von Web-Apps unterscheidet.)
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; }
Diejenigen, die mich überraschten, waren die (Wechsel zu) vergänglich für
Hinweis: Da IMySpecialObjectManager in "MySpecialObjectThatSpawnsThreads" injiziert wurde, mussten diese injizierten Objekte vorübergehend sein, um die Kette zu vervollständigen.
Der Punkt ist ....... es war nicht nur der (My) DbContext, der benötigt wurde .Transient ... sondern ein größerer Teil des DI-Graphen.
Debugging-Tipp:
Diese Linie:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Setzen Sie dort Ihren Debugger-Haltepunkt. Wenn Ihr MySpecialObjectThatSpawnsThreads N Threads erstellt (z. B. 10 Threads) ... und diese Zeile nur einmal getroffen wird ... ist das Ihr Problem. Ihr DbContext kreuzt Threads.
BONUS:
Ich würde vorschlagen, diese unten stehende URL / Artikel (Oldie but Goodie) über die Unterschiede zwischen Web-Apps und Konsolen-Apps zu lesen
https://mehdi.me/ambient-dbcontext-in-ef6/
Hier ist die Kopfzeile des Artikels, falls sich der Link ändert.
Ich habe dieses Problem mit WorkFlowCore festgestellt https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
Beispielcode unten .. um zukünftigen Internet-Suchern zu helfen
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)); } } }
und
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); }
quelle
In meinem Fall verwende ich eine Vorlagenkomponente in Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
Das Problem besteht darin, eine Methode (Count) im Komponentenheader aufzurufen. Um das Problem zu lösen, habe ich es folgendermaßen geändert:
int total = MyList.Count();
und später :
<BTable ID="Table1" TotalRows="total">
quelle
Ich weiß, dass dieses Problem vor zwei Jahren gestellt wurde, aber ich hatte gerade dieses Problem und das von mir verwendete Update hat wirklich geholfen.
Wenn Sie zwei Abfragen mit demselben Kontext ausführen, müssen Sie möglicherweise die entfernen
AsNoTracking
. Wenn Sie verwendenAsNoTracking
, erstellen Sie für jeden Lesevorgang einen neuen Datenleser. Zwei Datenleser können nicht dieselben Daten lesen.quelle
In meinem Fall habe ich eine Sperre verwendet, die die Verwendung von "Warten" nicht zulässt und keine Compiler-Warnung erstellt, wenn Sie nicht auf eine asynchrone Funktion warten.
Das Problem:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
Die Lösung: Warten Sie auf die Asynchronisierung im Schloss, indem Sie sie mit .Wait () blockieren.
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
quelle
Ein weiterer möglicher Fall: Wenn Sie die Verbindung direkt verwenden, vergessen Sie nicht, if zu schließen. Ich musste eine beliebige SQL-Abfrage ausführen und das Ergebnis lesen. Dies war eine schnelle Lösung. Ich wollte keine Datenklasse definieren und keine "normale" SQL-Verbindung einrichten. Also habe ich einfach die Datenbankverbindung von EFC als wiederverwendet
var connection = Context.Database.GetDbConnection() as SqlConnection
. Stellen Sie sicher, dass Sie anrufen,connection.Close()
bevor Sie dies tunContext.SaveChanges()
.quelle
Ich habe das gleiche Problem, wenn ich versuche,
FirstOrDefaultAsync()
die asynchrone Methode im folgenden Code zu verwenden. Und als ichFirstOrDefault()
das Problem behoben habe, war das Problem gelöst!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
quelle
Wenn Ihre Methode etwas zurückgibt, können Sie diesen Fehler beheben
.Result
, indem Sie das Ende des Jobs beenden und.Wait()
nichts zurückgeben.quelle
Ich habe es gerade geschafft, dass es wieder funktioniert. Es macht nicht viel Sinn, aber es hat funktioniert:
Ich werde später weiter nachforschen, aber die Methode, die ich mit Hangfire aufgerufen habe, erhält einen DBContext und das ist die mögliche Ursache.
quelle