Entity Framework Core: une deuxième opération a démarré sur ce contexte avant la fin d'une opération précédente

89

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?

André Luiz
la source
1
Vérifiez ceci .
Berkay
2
@Berkay J'ai vu cela et beaucoup d'autres questions similaires et je les ai essayées. Ma méthode était asynchrone et je l'ai synchronisée pour éviter ces problèmes. J'essaie également de supprimer le mappage, j'ai également essayé de supprimer le .ToPagedList, il continue de lancer l'erreur.
André Luiz
Ce serait bien de voir une trace de pile complète
Evk
Et pour savoir si plusieurs résultats actifs sont activés
Jay
Ayant eu le même problème, j'ai découvert que j'avais des entiers nullables dans ma table de base de données. dès que j'ai défini les propriétés de mon modèle d'entité pour correspondre à des int nullable, tout a commencé à fonctionner, donc les messages étaient trompeurs pour moi ...!
AlwaysLearning

Réponses:

87

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

services.AddTransient<MyContext>();

OU

services.AddDbContext<MyContext>(ServiceLifetime.Transient);

au lieu de

services.AddDbContext<MyContext>();

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' DbContextimplémentation n'est pas thread-safe. Vous pouvez en savoir plus ici

Alsami
la source
1
Lorsque j'utilise transitoire, j'obtiens des erreurs de connexion suivantes (fermées ou supprimées) «OmniService.DataAccess.Models.OmniServiceDbContext». System.ObjectDisposedException: impossible d'accéder à un objet supprimé. Une cause courante de cette erreur est de supprimer un contexte qui a été résolu à partir de l'injection de dépendances, puis d'essayer ultérieurement d'utiliser la même instance de contexte ailleurs dans votre application. Cela peut se produire si vous appelez Dispose () sur le contexte ou si vous encapsulez le contexte dans une instruction using. ... Nom de l'objet: 'AsyncDisposer'.
David
3
Salut David! Je suppose que vous utilisez 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épendance Task.Run. Consultez également ces liens. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…
alsami
3
Comme indiqué précédemment, si vous manquez d'appeler une méthode asynchrone avec le mot-clé await, vous serez confronté à ce problème.
Yennefer
1
Cela peut être bien, mais il faut être plus attentif à la durée de vie et à la résolution souhaitées de l'accès aux données que d'utiliser cavalièrement transitoire sans nuance des circonstances. En fait, je considère qu'il est rare que l'on veuille un contexte de données transitoire. Si la portée d'une unité de travail implique plus qu'une seule opération de données, la portée de la transaction doit s'étendre sur plus que cela. La résolution de votre contexte de données doit refléter la portée de votre unité de travail. C'est quelque chose qui doit être réfléchi et ce n'est pas une réponse universelle.
Dave Rael
3
@alsami tu es mon héros. 6 heures de débogage douloureux. C'était la solution. Si quelqu'un d'autre injecte IHttpContextAccessor dans le DbContext et que les revendications sont nulles, c'est la solution. Merci beaucoup mec.
jcmontx le
56

Dans certains cas, cette erreur se produit lors de l'appel d'une méthode asynchrone sans le awaitmot clé, qui peut simplement être résolue en ajoutant awaitavant 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.

Hamid Nasirloo
la source
4
Cela m'est arrivé. Changer First()pour await / FirstAsync()travaillé.
Guilherme
Vote positif. Voir également jimlynn.wordpress.com/2017/11/16/… Jim Lynn "ERREUR DE CADRE D'ENTITÉ: UNE DEUXIÈME OPÉRATION COMMENCÉE DANS CE CONTEXTE AVANT UNE OPÉRATION PRÉCÉDENTE TERMINÉE. TOUT MEMBRE D'INSTANCE NE SONT PAS GARANTIS POUR ÊTRE SÉCURISÉS."
granadaCoder le
Merci pour cela! C'était exactement mon problème ... J'ai oublié d'ajouter une attente sur une méthode asynchrone.
AxleWack
Cela m'est également arrivé, et ce commentaire m'a aidé pendant que je cherchais où j'avais oublié une attente manquante. Une fois que je l'ai trouvé, le problème est résolu.
Zion Hai
42

L'exception signifie qu'il _contextest utilisé par deux threads en même temps; soit deux threads dans la même demande, soit par deux demandes.

Votre _contextdéclaration est-elle statique peut-être? CA ne devrait pas être.

Ou appelez-vous GetClientsplusieurs 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 utiliserez AddDbContext()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.

Gabriel Luci
la source
1
C'est probablement mon travail. J'ai réussi à résoudre, voir ma réponse. Mais je marque le vôtre comme le bon
André Luiz
Mon code est exactement comme ça et nous suivons souvent "Une deuxième opération a démarré sur ce contexte avant qu'une opération asynchrone précédente ne soit terminée. Utilisez 'await' pour vous assurer que toutes les opérations asynchrones sont terminées avant d'appeler une autre méthode sur ce contexte. Les membres de l'instance ne le sont pas. garantie d'être thread-safe. - à System.Data.Entity.Internal.ThrowingMonitor.EnsureNotEntered () ".
NMathur le
@NMathur Utilisez-vous votre _contextobjet dans d'autres threads? Comme à l'intérieur d'un Task.Run()par exemple?
Gabriel Luci le
@GabrielLuci toutes mes méthodes sont asynchrones comme ci-dessous, cela causera-t-il le problème. Mes connaissances sur ce sujet sont très limitées. Pouvez-vous suggérer où et que dois-je lire en détail pour comprendre ces comportements? public async Task <List <Item>> GetItems (int orderId) {List <Item> items = wait _context.Item.Where (x => x.OrderId == orderId) .ToListAsync (); retourner les articles; }
NMathur le
@NMathur Ça a l'air bien. Assurez-vous simplement que vous utilisez toujours des awaitméthodes asynchrones. Si vous n'utilisez pas await, vous pouvez involontairement entrer dans le multi-threading.
Gabriel Luci le
8
  • 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);
    
Muhammad Armaghan
la source
6

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

  1. N'injectez jamais de services Scoped & Transient dans le service Singleton. (Cela convertit efficacement le service transitoire ou étendu en singleton.)

  2. Ne jamais injecter de services transitoires dans le service de portée (cela convertit le service transitoire en service de portée.)

DiPix
la source
c'était exactement mon problème
Jonesopolis
C'était aussi mon problème. J'enregistrais une classe de gestionnaire comme singleton et le DbContext comme transitoire. J'ai dû utiliser ServiceProvider dans la classe Handler pour obtenir une instance transitoire du conteneur DI chaque fois que le Handler est touché
Daiana Sodré
5

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 de public async Task ....

Raynlaze
la source
4

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 IQueryableen List(ou en tableau, collection ...).

Par exemple:

var list=_context.table1.where(...);

à

var list=_context.table1.where(...).ToList(); //or ToArray()...
Makhidi Masimov
la source
2
IMHO, Cette réponse ne mérite pas moins de points, elle est juste mal exprimée. .ToList () résout en effet la plupart des problèmes "une seconde opération ..." du fait qu'elle force l'évaluation immédiate de l'expression. De cette façon, il n'y a pas d'opérations de contexte de mise en file d'attente.
vassilag le
C'était le problème dans mon cas. J'avais xxx.Contains (z.prop) dans une clause where d'une requête. xxx était censé être un tableau int [] distinct résolu à partir d'une requête précédente. Malheureusement, au moment où la deuxième requête a été lancée, xxx était toujours un IQueryable. L'ajout de xxx.ToArray () avant la deuxième requête a résolu mon problème.
Jason Butera
2

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();
Francisco Goldenstein
la source
Le contexte ne serait-il pas exposé uniquement une fois que tout le bloc using a terminé son exécution?
Sello Mkantjwa
Lorsque l'action est supprimée, tout est supprimé dans cette étendue. Si vous avez une tâche en cours d'exécution en arrière-plan et que cette tâche est plus longue que l'action, vous rencontrerez ce problème à moins que vous ne créiez une nouvelle portée pour la tâche, comme je l'ai fait dans l'exemple. D'un autre côté, si votre tâche peut prendre du temps ou si vous voulez être sûr à 100% qu'elle s'exécutera, vous devrez peut-être utiliser une file d'attente. Si vous utilisez Azure, vous pouvez utiliser les files d'attente Service Bus.
Francisco Goldenstein
2

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 des asyncrequêtes et toute utilisation simultanée explicite à partir de plusieurs threads. Par conséquent, await asyncappelle toujours immédiatement ou utilisez des DbContextinstances distinctes pour les opérations qui s'exécutent en parallèle.

Ehsan Gondal
la source
1

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 à:

UserManager.FindByNameAsync(username).Result
UserManager.CreateAsync(user, pass).Result
UserManager.AddToRoleAsync(user, roleName).Result

Lorsque j'ai remplacé .Resultpar .GetAwaiter().GetResult(), cette erreur a disparu.

Catalin
la source
0

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

Sharon Zhou
la source
0

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

services.AddDbContext<DbContext>(ServiceLifetime.Transient);

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.

NiPfi
la source
0

J'ai réussi à obtenir cette erreur en passant un IQueryabledans 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 SecondMethoden êtreSecondMethod(stockItems.ToList()

tomRedox
la source
Cela a résolu le problème, mais cela ne ralentira-t-il pas les performances. Existe-t-il une solution alternative?
Dheeraj Kumar
0

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)

Une ou plusieurs erreurs se sont produites. (Une deuxième opération a démarré sur ce contexte avant la fin d'une opération précédente. Cela est généralement dû à différents threads utilisant la même instance de DbContext. Pour plus d'informations sur la façon d'éviter les problèmes de thread avec DbContext, voir https://go.microsoft.com / fwlink /? linkid = 2097913. )

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

        .AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
    .AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()

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.

GÉRER DBCONTEXT DE LA BONNE FAÇON AVEC ENTITY FRAMEWORK 6: UN GUIDE EN PROFONDEUR Mehdi El Gueddari

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);
                }
grenadeCoder
la source
0

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">
Ali Borjian
la source
0

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, AsNoTrackingvous 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.

Cornelis de Jager
la source
0

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();
}
Tikall
la source
0

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'appeler connection.Close()avant de le faire Context.SaveChanges().

klenium
la source
-1

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;
...
JuSik404
la source
1
Il n'est pas du tout lié à FirstOrDefault () ou FirstOrDefaultAsync (), mais à l'utilisation de dbContext.
sajadre le
-2

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.

Hasan_H
la source
-6

J'ai juste réussi à le faire fonctionner à nouveau. Cela n'a pas beaucoup de sens mais cela a fonctionné:

  1. Supprimer Hangfire de StartUp (j'y créais mon travail)
  2. Suppression de la base de données hangfire
  3. Redémarré le serveur

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.

André Luiz
la source