Je travaille sur un projet ASP.Net Core 2.0 utilisant 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"/>
Et dans l'une de mes méthodes de liste, j'obtiens cette erreur:
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()
Voici ma méthode:
[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;
}
Je suis un peu perdu, surtout parce que cela fonctionne lorsque je l'exécute localement, mais lorsque je déploie sur mon serveur de transfert (IIS 8.5), cela me donne cette erreur et cela fonctionnait normalement. L'erreur a commencé à apparaître après avoir augmenté la longueur maximale de l'un de mes modèles. J'ai également mis à jour la longueur maximale du modèle de vue correspondant. Et il existe de nombreuses autres méthodes de liste qui sont très similaires et qui fonctionnent.
J'avais un travail Hangfire en cours d'exécution, mais ce travail n'utilise pas la même entité. C'est tout ce que je pense être pertinent. Des idées sur ce qui pourrait en être la cause?
la source
Réponses:
Je ne suis pas sûr que vous utilisiez IoC et Dependency Injection pour résoudre votre DbContext partout où il pourrait être utilisé. Si vous le faites et que vous utilisez l'IoC natif de .NET Core (ou tout autre conteneur IoC) et que vous obtenez cette erreur, assurez-vous d'enregistrer votre DbContext en tant que Transient. Faire
OU
au lieu de
AddDbContext ajoute le contexte tel que défini, ce qui peut entraîner des problèmes lors de l'utilisation de plusieurs threads.
Les opérations async / await peuvent également provoquer ce comportement, lors de l'utilisation d'expressions lambda asynchrones.
L'ajouter comme transitoire a également ses inconvénients. Vous ne pourrez pas apporter de modifications à une entité sur plusieurs classes qui utilisent le contexte car chaque classe obtiendra sa propre instance de votre DbContext.
L'explication simple à cela est que l'
DbContext
implémentation n'est pas thread-safe. Vous pouvez en savoir plus icila source
Task.Run(async () => context.Set...)
sans l'attendre ou en créant un contexte de base de données de portée sans attendre le résultat. Cela signifie que votre contexte est probablement déjà supprimé lors de son accès. Si vous utilisez Microsoft DI, vous devez créer vous-même une étendue de dépendanceTask.Run
. Consultez également ces liens. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…Dans certains cas, cette erreur se produit lors de l'appel d'une méthode asynchrone sans le
await
mot clé, qui peut simplement être résolue en ajoutantawait
avant l'appel de la méthode. cependant, la réponse peut ne pas être liée à la question mentionnée, mais elle peut aider à résoudre une erreur similaire.la source
First()
pourawait / FirstAsync()
travaillé.L'exception signifie qu'il
_context
est utilisé par deux threads en même temps; soit deux threads dans la même demande, soit par deux demandes.Votre
_context
déclaration est-elle statique peut-être? CA ne devrait pas être.Ou appelez-vous
GetClients
plusieurs fois dans la même demande depuis un autre endroit dans votre code?Vous le faites peut-être déjà, mais idéalement, vous utiliseriez l' injection de dépendances pour votre
DbContext
, ce qui signifie que vous utiliserezAddDbContext()
dans votre Startup.cs, et votre constructeur de contrôleur ressemblera à ceci:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Si votre code n'est pas comme ça, montrez-nous et peut-être que nous pouvons vous aider davantage.
la source
_context
objet dans d'autres threads? Comme à l'intérieur d'unTask.Run()
par exemple?await
méthodes asynchrones. Si vous n'utilisez pasawait
, vous pouvez involontairement entrer dans le multi-threading.Résolvez mon problème en utilisant cette ligne de code dans mon fichier Startup.cs.
L'ajout d'un service transitoire signifie qu'à chaque fois que le service est demandé, une nouvelle instance est créée lorsque vous travaillez avec l'injection de dépendances
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
la source
J'ai eu le même problème et il s'est avéré que le service parental était un célibataire. Donc, le contexte est devenu automatiquement singelton aussi. Même si a été déclaré comme par durée de vie scoped dans DI
Injecter un service avec des durées de vie différentes dans un autre
N'injectez jamais de services Scoped & Transient dans le service Singleton. (Cela convertit efficacement le service transitoire ou étendu en singleton.)
Ne jamais injecter de services transitoires dans le service de portée (cela convertit le service transitoire en service de portée.)
la source
J'ai eu la même erreur. C'est arrivé parce que j'ai appelé une méthode qui a été construite comme
public async void ...
au lieu depublic async Task ...
.la source
Je pense que cette réponse peut encore aider quelqu'un et sauver plusieurs fois. J'ai résolu un problème similaire en changeant
IQueryable
enList
(ou en tableau, collection ...).Par exemple:
var list=_context.table1.where(...);
à
var list=_context.table1.where(...).ToList(); //or ToArray()...
la source
J'ai été confronté au même problème, mais la raison n'était aucune de celles énumérées ci-dessus. J'ai créé une tâche, créé une portée à l'intérieur de la tâche et demandé au conteneur d'obtenir un service. Cela a bien fonctionné, mais j'ai ensuite utilisé un deuxième service dans la tâche et j'ai oublié de le demander également à la nouvelle portée. Pour cette raison, le deuxième service utilisait un DbContext qui était déjà supprimé.
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. ... } }
J'aurais dû faire ceci:
var otherProductService = scope.ServiceProvider.GetService<IProductService>(); otherProductService.DoSomething();
la source
Entity Framework Core ne prend pas en charge plusieurs opérations parallèles exécutées sur le même
DbContext
instance. Cela inclut à la fois l'exécution parallèle desasync
requêtes et toute utilisation simultanée explicite à partir de plusieurs threads. Par conséquent,await async
appelle toujours immédiatement ou utilisez desDbContext
instances distinctes pour les opérations qui s'exécutent en parallèle.la source
Ma situation est différente: j'essayais de semer la base de données avec 30 utilisateurs, appartenant à des rôles spécifiques, alors j'exécutais ce code:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
C'était une fonction de synchronisation. À l'intérieur, j'ai eu 3 appels à:
Lorsque j'ai remplacé
.Result
par.GetAwaiter().GetResult()
, cette erreur a disparu.la source
J'ai reçu le même message. Mais cela n'a aucun sens dans mon cas. Mon problème est que j'ai utilisé une propriété "NotMapped" par erreur. Cela signifie probablement seulement une erreur de syntaxe Linq ou de classe de modèle dans certains cas. Le message d'erreur semble trompeur. La signification originale de ce message est que vous ne pouvez pas appeler async sur le même dbcontext plus d'une fois dans la même requête.
[NotMapped] public int PostId { get; set; } public virtual Post Post { get; set; }
Vous pouvez consulter ce lien pour plus de détails, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
la source
J'ai un service d'arrière-plan qui effectue une action pour chaque entrée dans une table. Le problème est que si j'itère et modifie certaines données sur la même instance du DbContext, cette erreur se produit.
Une solution, comme mentionné dans ce fil, consiste à changer la durée de vie du DbContext en transitoire en le définissant comme
mais parce que je fais des changements dans plusieurs services différents et que je les engage à la fois en utilisant la
SaveChanges()
méthode, cette solution ne fonctionne pas dans mon cas.Parce que mon code s'exécute dans un service, je faisais quelque chose comme
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); } }
pour pouvoir utiliser le service comme s'il s'agissait d'une simple demande. Donc, pour résoudre le problème, je divise simplement la portée unique en deux, un pour la requête et l'autre pour les opérations d'écriture comme ceci:
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); } }
Comme ça, il y a effectivement deux instances différentes du DbContext utilisées.
Une autre solution possible serait de s'assurer que l'opération de lecture est terminée avant de démarrer l'itération. Ce n'est pas très pratique dans mon cas car il pourrait y avoir beaucoup de résultats qui auraient tous besoin d'être chargés en mémoire pour l'opération que j'ai essayé d'éviter en utilisant un Queryable en premier lieu.
la source
J'ai réussi à obtenir cette erreur en passant un
IQueryable
dans une méthode qui a ensuite utilisé cette «liste» IQueryable dans le cadre d'une autre requête dans le même contexte.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(); }
Pour arrêter cela, j'ai utilisé l' approche ici et matérialisé cette liste avant de lui passer la deuxième méthode, en changeant l'appel
SecondMethod
en êtreSecondMethod(stockItems.ToList()
la source
Tout d'abord, votez pour (au moins) la réponse d'alsami. Cela m'a mis sur la bonne voie.
Mais pour ceux d'entre vous qui font l'IoC, voici un peu plus de détails.
Mon erreur (comme les autres)
Ma configuration de code. "Juste les bases" ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
et
public interface IMySpecialObjectDomainData{}
et (notez que MyCoolDbContext est en cours d'injection)
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); } }
et
public interface IMySpecialObjectManager{}
et
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); } }
Et enfin, ma classe multithread, appelée depuis une application console (application d'interface de ligne de commande)
public interface IMySpecialObjectThatSpawnsThreads{}
et
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); } }
et l'accumulation de DI. (Encore une fois, c'est pour une application console (interface de ligne de commande) ... qui présente un comportement légèrement différent de celui des applications 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; }
Ceux qui m'ont surpris étaient le (passage à) transitoire pour
Remarque, je pense que parce que IMySpecialObjectManager était injecté dans "MySpecialObjectThatSpawnsThreads", ces objets injectés devaient être transitoires pour compléter la chaîne.
Le point étant ....... ce n'était pas seulement le (My) DbContext qui avait besoin de .Transient ... mais une plus grande partie du graphique DI.
Conseil de débogage:
Cette ligne:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Mettez votre point d'arrêt du débogueur ici. Si votre MySpecialObjectThatSpawnsThreads fait N nombre de threads (disons 10 threads par exemple) ...... et que cette ligne n'est frappée qu'une seule fois ... c'est votre problème. Votre DbContext croise les threads.
PRIME:
Je suggérerais de lire ceci ci-dessous url / article (oldie mais goodie) sur les différences entre les applications Web et les applications de console
https://mehdi.me/ambient-dbcontext-in-ef6/
Voici l'en-tête de l'article au cas où le lien changerait.
J'ai rencontré ce problème avec WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
exemple de code ci-dessous .. pour aider les futurs internautes
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)); } } }
et
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); }
la source
Dans mon cas, j'utilise un composant de modèle dans Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
Le problème appelle une méthode (Count) dans l'en-tête du composant. Pour résoudre le problème, je l'ai changé comme ceci:
int total = MyList.Count();
et ensuite :
<BTable ID="Table1" TotalRows="total">
la source
Je sais que ce problème a été posé il y a deux ans, mais je viens d'avoir ce problème et le correctif que j'ai utilisé a vraiment aidé.
Si vous effectuez deux requêtes avec le même contexte, vous devrez peut-être supprimer le fichier
AsNoTracking
. Si vous utilisez,AsNoTracking
vous créez un nouveau lecteur de données pour chaque lecture. Deux lecteurs de données ne peuvent pas lire les mêmes données.la source
Dans mon cas, j'utilisais un verrou qui n'autorise pas l'utilisation de await et ne crée pas d'avertissement du compilateur lorsque vous n'attendez pas une asynchrone.
Le problème:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
Le correctif: attendez l'asynchrone à l'intérieur du verrou en le bloquant avec un .Wait ()
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
la source
Autre cas possible: si vous utilisez la connexion directe, n'oubliez pas de fermer si. J'avais besoin d'exécuter une requête SQL arbitraire et de lire le résultat. C'était une solution rapide, je ne voulais pas définir une classe de données, pas configurer une connexion SQL "normale". J'ai donc simplement réutilisé la connexion à la base de données d'EFC en tant que
var connection = Context.Database.GetDbConnection() as SqlConnection
. Assurez-vous d'appelerconnection.Close()
avant de le faireContext.SaveChanges()
.la source
J'ai eu le même problème lorsque j'essaie d'utiliser
FirstOrDefaultAsync()
la méthode async dans le code ci-dessous. Et quand j'ai réparéFirstOrDefault()
- le problème était résolu!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
la source
Si votre méthode renvoie quelque chose, vous pouvez résoudre cette erreur en mettant
.Result
à la fin du travail et.Wait()
si elle ne renvoie rien.la source
J'ai juste réussi à le faire fonctionner à nouveau. Cela n'a pas beaucoup de sens mais cela a fonctionné:
J'examinerai plus loin plus tard, mais la méthode que j'ai appelée avec hangfire reçoit un DBContext et c'est la cause possible.
la source