Pracuję nad projektem ASP.Net Core 2.0 przy użyciu 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"/>
W jednej z moich metod listy otrzymuję ten błąd:
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()
To jest moja metoda:
[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;
}
Jestem trochę zagubiony, zwłaszcza, że działa, gdy uruchamiam go lokalnie, ale kiedy wdrażam na moim serwerze przejściowym (IIS 8.5), pojawia się ten błąd i działa normalnie. Błąd zaczął się pojawiać po zwiększeniu maksymalnej długości jednego z moich modeli. Zaktualizowałem również maksymalną długość odpowiedniego modelu widoku. Istnieje wiele innych metod tworzenia list, które są bardzo podobne i działają.
Miałem uruchomioną pracę Hangfire, ale ta praca nie używa tej samej jednostki. To wszystko, co uważam za istotne. Jakieś pomysły, co może to powodować?
c#
entity-framework
asp.net-web-api
André Luiz
źródło
źródło
Odpowiedzi:
Nie jestem pewien, czy używasz IoC i Dependency Injection, aby rozwiązać swój kontekst DbContext, gdziekolwiek może być używany. Jeśli to zrobisz i używasz natywnego IoC z .NET Core (lub dowolnego innego kontenera IoC) i otrzymujesz ten błąd, pamiętaj, aby zarejestrować swój DbContext jako przejściowy. Zrobić
LUB
zamiast
AddDbContext dodaje kontekst jako określony w zakresie, co może powodować problemy podczas pracy z wieloma wątkami.
Również operacje async / await mogą powodować to zachowanie w przypadku korzystania z wyrażeń lambda asynchronicznego.
Dodanie go jako przejściowego ma również swoje wady. Nie będzie można wprowadzać zmian w niektórych jednostkach w wielu klasach używających kontekstu, ponieważ każda klasa otrzyma własne wystąpienie DbContext.
Prostym wytłumaczeniem jest to, że
DbContext
implementacja nie jest bezpieczna wątkowo. Możesz przeczytać więcej na ten temat tutajźródło
Task.Run(async () => context.Set...)
bez czekania na to lub tworzenia ograniczonego kontekstu db bez oczekiwania na wynik. Oznacza to, że Twój kontekst jest prawdopodobnie już usunięty podczas uzyskiwania do niego dostępu. Jeśli korzystasz z Microsoft DI, musisz samodzielnie utworzyć zakres zależności w tymTask.Run
. Sprawdź również te linki. stackoverflow.com/questions/45047877/ ... docs.microsoft.com/en-us/dotnet/api/ ...W niektórych przypadkach ten błąd występuje podczas wywoływania metody asynchronicznej bez
await
słowa kluczowego, co można po prostu rozwiązać, dodającawait
przed wywołaniem metody. jednak odpowiedź może nie być związana z wymienionym pytaniem, ale może pomóc w rozwiązaniu podobnego błędu.źródło
First()
naawait / FirstAsync()
pracę.Wyjątek oznacza, że
_context
jest używany przez dwa wątki w tym samym czasie; albo dwa wątki w tym samym żądaniu, albo przez dwa żądania.Czy Twoja
_context
deklarowana statyczność może być? Nie powinno być.A może dzwonisz
GetClients
wiele razy w ramach tego samego żądania z innego miejsca w swoim kodzie?Być może już to robisz, ale idealnie byłoby, gdybyś używał iniekcji zależności dla swojego
DbContext
, co oznacza, że będziesz używaćAddDbContext()
w swoim Startup.cs, a konstruktor kontrolera będzie wyglądał mniej więcej tak:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Jeśli Twój kod nie jest taki, pokaż nam, a może pomożemy Ci dalej.
źródło
_context
obiektu w innych wątkach? JakTask.Run()
na przykład w środku ?await
z metod asynchronicznych. Jeśli nie używaszawait
, możesz przypadkowo włączyć się w wielowątkowość.Rozwiąż mój problem, używając tej linii kodu w moim pliku Startup.cs.
Dodanie usługi przejściowej oznacza, że za każdym razem, gdy usługa jest żądana, tworzone jest nowe wystąpienie podczas pracy z iniekcją zależności
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
źródło
Miałem ten sam problem i okazało się, że usługa rodzicielska to singelton. Zatem kontekst automatycznie stał się również singeltonowy. Mimo że został zadeklarowany jako Per Life Time Scoped w DI.
Wstrzykiwanie usługi o różnych okresach życia do innej
Nigdy nie wprowadzaj usług Scoped & Transient do usługi Singleton. (To skutecznie konwertuje usługę przejściową lub o określonym zakresie na singleton).
Nigdy nie wstrzykuj usług przejściowych do usługi w zakresie (to konwertuje usługę przejściową do zakresu).
źródło
Miałem ten sam błąd. Stało się tak, ponieważ wywołałem metodę, która została skonstruowana jako
public async void ...
zamiastpublic async Task ...
.źródło
Myślę, że ta odpowiedź może komuś pomóc i wiele razy uratować. Rozwiązałem podobny problem, zmieniając
IQueryable
naList
(lub na tablicę, kolekcję ...).Na przykład:
var list=_context.table1.where(...);
do
var list=_context.table1.where(...).ToList(); //or ToArray()...
źródło
Napotkałem ten sam problem, ale powodem nie był żaden z wymienionych powyżej. Utworzyłem zadanie, utworzyłem zakres w zadaniu i poprosiłem kontener o uzyskanie usługi. To działało dobrze, ale potem użyłem drugiej usługi w zadaniu i zapomniałem również poprosić o to do nowego zakresu. Z tego powodu druga usługa korzystała z DbContext, który został już usunięty.
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. ... } }
Powinienem był to zrobić:
var otherProductService = scope.ServiceProvider.GetService<IProductService>(); otherProductService.DoSomething();
źródło
Entity Framework Core nie obsługuje wielu równoległych operacji uruchamianych w tym samym
DbContext
wystąpieniu. Obejmuje to zarówno równoległe wykonywanieasync
zapytań, jak i dowolne jawne współbieżne użycie z wielu wątków. Dlatego zawszeawait async
wywołuje natychmiast lub używaj oddzielnychDbContext
wystąpień dla operacji wykonywanych równolegle.źródło
Moja sytuacja jest inna: próbowałem zalać bazę danych 30 użytkownikami należącymi do określonych ról, więc uruchomiłem ten kod:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
To była funkcja synchronizacji. Wewnątrz miałem 3 telefony do:
Kiedy otrzymuje
.Result
się.GetAwaiter().GetResult()
ten błąd odszedł.źródło
Otrzymałem tę samą wiadomość. Ale w moim przypadku to nie ma sensu. Mój problem polega na tym, że przez pomyłkę użyłem właściwości „NotMapped”. Prawdopodobnie oznacza to tylko błąd składni Linq lub klasy modelu w niektórych przypadkach. Komunikat o błędzie wydaje się mylący. Pierwotne znaczenie tego komunikatu polega na tym, że nie można wywołać async w tym samym kontekście dbcontext więcej niż raz w tym samym żądaniu.
[NotMapped] public int PostId { get; set; } public virtual Post Post { get; set; }
Możesz sprawdzić ten link, aby uzyskać szczegółowe informacje, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
źródło
Mam usługę w tle, która wykonuje akcję dla każdego wpisu w tabeli. Problem polega na tym, że jeśli iteruję i modyfikuję niektóre dane w tym samym wystąpieniu DbContext, ten błąd występuje.
Jednym z rozwiązań, jak wspomniano w tym wątku, jest zmiana czasu życia DbContext na przejściowy przez zdefiniowanie go tak, jak
ale ponieważ robię zmiany w wielu różnych usługach i zatwierdzam je na raz przy użyciu
SaveChanges()
metody, to rozwiązanie nie działa w moim przypadku.Ponieważ mój kod działa w usłudze, robiłem coś takiego
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); } }
aby móc korzystać z usługi tak, jakby to była prosta prośba. Aby rozwiązać ten problem, po prostu podzielę pojedynczy zakres na dwa, jeden dla zapytania, a drugi dla operacji zapisu, takich jak:
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); } }
W ten sposób istnieją efektywnie dwa różne wystąpienia używanego DbContext.
Innym możliwym rozwiązaniem byłoby upewnienie się, że operacja odczytu została zakończona przed rozpoczęciem iteracji. W moim przypadku nie jest to zbyt praktyczne, ponieważ może być wiele wyników, które musiałyby zostać załadowane do pamięci dla operacji, której starałem się uniknąć, używając w pierwszej kolejności Queryable.
źródło
Udało mi się uzyskać ten błąd, przekazując metodę
IQueryable
do metody, która następnie używała tej „listy” IQueryable jako części innego zapytania w tym samym kontekście.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(); }
Aby zatrzymać że dzieje użyłem podejście tutaj i zmaterializował ten wykaz przed przekazaniem go drugą metodę, zmieniając wezwanie do
SecondMethod
byćSecondMethod(stockItems.ToList()
źródło
Najpierw zagłosuj za (przynajmniej) odpowiedź alsami. To zaprowadziło mnie na właściwą ścieżkę.
Ale dla tych z was, którzy zajmują się IoC, tutaj jest trochę głębsze nurkowanie.
Mój błąd (taki sam jak inne)
Moja konfiguracja kodu. „Tylko podstawy” ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
i
public interface IMySpecialObjectDomainData{}
i (uwaga MyCoolDbContext jest wstrzykiwany)
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); } }
i
public interface IMySpecialObjectManager{}
i
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); } }
I wreszcie moja wielowątkowa klasa wywoływana z aplikacji konsoli (aplikacja interfejsu wiersza poleceń)
public interface IMySpecialObjectThatSpawnsThreads{}
i
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); } }
i budowanie DI. (Ponownie dotyczy to aplikacji konsoli (interfejsu wiersza poleceń) ... która wykazuje nieco inne zachowanie niż aplikacje internetowe)
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; }
Te, które mnie zaskoczyły, to (zmiana na) przejściowe dla
Uwaga, myślę, że ponieważ IMySpecialObjectManager był wstrzykiwany do „MySpecialObjectThatSpawnsThreads”, te wstrzyknięte obiekty musiały być przejściowe, aby zakończyć łańcuch.
Chodziło o to, że ....... potrzebował nie tylko (My) DbContext .Transient ... ale większy fragment Di Graph.
Wskazówka dotycząca debugowania:
Ta linia:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Umieść tam punkt przerwania debugera. Jeśli Twój MySpecialObjectThatSpawnsThreads tworzy N liczbę wątków (na przykład 10 wątków) ...... i ta linia jest trafiona tylko raz ... to jest twój problem. Twój DbContext przecina wątki.
PREMIA:
Proponuję przeczytać poniższy adres url / artykuł (stary, ale dobry) o różnicach między aplikacjami internetowymi i aplikacjami konsolowymi
https://mehdi.me/ambient-dbcontext-in-ef6/
Oto nagłówek artykułu na wypadek zmiany linku.
Trafiłem na ten problem z WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
przykładowy kod poniżej .. aby pomóc przyszłym wyszukiwarkom internetowym
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)); } } }
i
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); }
źródło
W moim przypadku używam komponentu szablonu w Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
Problem polega na wywołaniu metody (Count) w nagłówku komponentu. Aby rozwiązać problem, zmieniłem to w następujący sposób:
int total = MyList.Count();
i później :
<BTable ID="Table1" TotalRows="total">
źródło
Wiem, że ten problem został zadany dwa lata temu, ale właśnie miałem ten problem i poprawka, której użyłem, naprawdę pomogła.
Jeśli wykonujesz dwa zapytania z tym samym kontekstem - może być konieczne usunięcie
AsNoTracking
. Jeśli używaszAsNoTracking
, tworzysz nowy czytnik danych dla każdego odczytu. Dwa czytniki danych nie mogą odczytać tych samych danych.źródło
W moim przypadku użyłem blokady, która nie pozwala na użycie await i nie tworzy ostrzeżenia kompilatora, gdy nie czekasz na asynchroniczność.
Problem:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
Poprawka: poczekaj na asynchronizację wewnątrz zamka, blokując ją za pomocą .Wait ()
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
źródło
Inny możliwy przypadek: jeśli korzystasz z połączenia bezpośredniego, nie zapomnij zamknąć if. Musiałem wykonać dowolne zapytanie SQL i odczytać wynik. To była szybka naprawa, nie chciałem definiować klasy danych, nie ustawiać „normalnego” połączenia SQL. Po prostu ponownie użyłem połączenia z bazą danych EFC jako
var connection = Context.Database.GetDbConnection() as SqlConnection
. Pamiętaj, aby zadzwonić,connection.Close()
zanim to zrobiszContext.SaveChanges()
.źródło
Mam ten sam problem, gdy próbuję użyć
FirstOrDefaultAsync()
metody asynchronicznej w poniższym kodzie. A kiedy naprawiłemFirstOrDefault()
- problem został rozwiązany!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
źródło
Jeśli Twoja metoda zwraca coś z powrotem, możesz rozwiązać ten błąd, umieszczając
.Result
na końcu zadania i.Wait()
jeśli nic nie zwraca.źródło
Po prostu udało mi się sprawić, że znowu zadziała. Nie ma to większego sensu, ale zadziałało:
Zbadam dalej, ale metoda, którą wywołałem z hangfire, otrzymuje DBContext i to jest możliwa przyczyna.
źródło