222

I'm working on a ASP.Net Core 2.0 project using 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"/>

And in one of my list methods I'm getting this error:

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()

This is my method:

    [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;
    }

I'm a bit lost especially because it works when I run it locally, but when I deploy to my staging server (IIS 8.5) it gets me this error and it was working normally. The error started to appear after I increase the max length of one of my models. I also updated the max length of the corresponding View Model. And there are many other list methods that are very similar and they are working.

I had a Hangfire job running, but this job doesn't use the same entity. That's all I can think to be relevant. Any ideas of what could be causing this?

Majid Basirati
  • 2,665
  • 3
  • 24
  • 46
André Luiz
  • 6,642
  • 9
  • 55
  • 105
  • 2
    Check [this](https://stackoverflow.com/questions/40363807/a-second-operation-started-on-this-context-before-a-previous-asynchronous-operat?rq=1). – Berkay Yaylacı Feb 13 '18 at 13:29
  • 2
    @Berkay I saw that and many other similar questions and tried them. My method was async and I made it sync to avoid these issues. I also tries to remove the mapping, also tried to remove the .ToPagedList it continues throwing the error. – André Luiz Feb 13 '18 at 13:34
  • Having had the same problem I discovered I had nullable integers in my database table. as soon as I set my entity model properties to matching nullable int's, it all started working so, the messages were misleading for me...! – AlwaysLearning Aug 14 '19 at 12:27
  • 1
    As often happens with such general exceptions, people start listing their own cases where the same exception occurs. Please, don't. It's irrelevant. What matters is that a context is used in multiple threads which is never a good idea. There can be hundreds of scenarios where this is the root cause of the exception. – Gert Arnold Jan 11 '21 at 08:11
  • Use `async Task<>` everywhere and don't forget to `await`. – Ash May 15 '23 at 03:55

20 Answers20

208

I am not sure if you are using IoC and Dependency Injection to resolve your DbContext where ever it might be used. If you do and you are using native IoC from .NET Core (or any other IoC-Container) and you are getting this error, make sure to register your DbContext as Transient. Do

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

OR

services.AddTransient<MyContext>();

instead of

services.AddDbContext<MyContext>();

AddDbContext adds the context as scoped, which might cause troubles when working with multiple threads.

Also async / await operations can cause this behaviour, when using async lambda expressions.

Adding it as transient also has its downsides. You will not be able to make changes to some entity over multiple classes that are using the context because each class will get its own instance of your DbContext.

The simple explanation for that is, that the DbContext implementation is not thread-safe. You can read more about this here

alsami
  • 8,996
  • 3
  • 25
  • 36
  • 2
    When I use transient i get following connection errors (closed or disposed) 'OmniService.DataAccess.Models.OmniServiceDbContext'. System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. ... Object name: 'AsyncDisposer'. – David Oct 26 '18 at 04:35
  • 12
    Hi @David! I guess you are using `Task.Run(async () => context.Set...)` without awaiting it or creating a scoped db context without awaiting the result. This means your context is probably already disposed when accessing it. If you are on Microsoft DI, you must create a dependency scope yourself within that `Task.Run`. Check out these links as well. https://stackoverflow.com/questions/45047877/asynchronous-method-with-context-dispose https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory.createscope?view=aspnetcore-2.1 – alsami Oct 26 '18 at 04:42
  • 6
    As pointed out earlier, if you miss to call an async method with the await keyword, you'll face this issue. – Yennefer Dec 05 '18 at 13:34
  • This helped me. I am injecting dependencies on a new thread from a hosted service when we receive a new message from a queue. Making my context transient fixed the exception – Cale Jun 11 '19 at 23:18
  • This is the one that helped me! My context is not static. I added ServiceLifetime.Tansient in my AddDbContext in Startup class and worked like a charm – letie Jul 09 '19 at 13:58
  • @LetieTechera great! Yeah, is unlikely that people still do use statics but it happens. – alsami Jul 09 '19 at 15:17
  • If using a `TransactionScope` this can cause an exception "This platform does not support distributed transactions." in certain circumstances. E.g. if using multiple Mediatr requests within a transaction scope. – br3nt Aug 07 '19 at 04:56
  • Would you mind elaborate a little more? I could add it to the answer. – alsami Aug 07 '19 at 06:42
  • Is it safe to use? I mean: setting ServiceLifetime to Transient will bypass this error creating multiple instances of DbContext, but what happens when multiple threads save data to database at the same time? Can this solution cause any problems? – Piotrek Oct 07 '19 at 19:21
  • @Piotrek as I mentioned that this will be one downside. You will not have shared context throughout a request lifetime so you cannot make changes to one entity over multiple classes, which you shouldn't anyway. I have used this approach in a rather back application (2million LoC) and haven't had any problems. – alsami Oct 08 '19 at 05:25
  • 2
    This can be fine, but one must be more thoughtful about the desired lifetime and resolution scope of data access than to cavalierly use transient without nuance of the circumstances. In fact, I'd consider it rare that one would want a data context transient. If the scope of a unit of work is something involving more than a single data operation, transaction scope should span more than that. Resolution of your data context should mirror the scope of your unit of work. This is something that should be thought through and this is not a one-size-fits-all answer. – Dave Rael Oct 15 '19 at 14:03
  • 3
    @alsami you are my hero. 6 hours of painful debugging. This was the solution. If someone else is injecting IHttpContextAccessor into the DbContext and the Claims are null, this is the solution. Thank you very much man. – jcmontx Jun 03 '20 at 00:20
  • If transient registration helps you're maybe using your Context the wrong way. Inform yourself about UnitOfWork pattern.. – Andreas Gyr Jul 23 '20 at 08:49
  • 1
    Unit of work and repository pattern is already implemented by entity-framework core, no need to repeat that. EF is not thread-safe, please stop spreading misinformation. – alsami Jul 23 '20 at 12:31
  • Thanks! My DbContext was indeed wired as Scoped in stead of Transient. Which meant a lot of static resource calls threw 500 errors cause the back-end was throwing context errors. – Creative Jan 26 '21 at 11:36
  • 2
    @AndreLuz This is the correct answer and should be accepted as such. At any rate, it earned alsami the `Unsung Hero` award. – J Weezy Mar 24 '21 at 18:41
  • 2
    @jcmontx "If someone else is injecting IHttpContextAccessor into the DbContext" then they are doing things **very, very** wrong and deserve whatever pain they get. – Ian Kemp Jul 26 '21 at 13:32
  • I ran into this error because I created a `List>`. The single tasks used the injected DBContext. First I tried to call await Task.WhenAll(...) and hit the error. But then I awaited them one after another in a for loop and still ran into the error. So it's important to not only await them sequentially but also to create the tasks sequentially! – koks der drache Jan 12 '23 at 18:30
  • Thank you, with dependency injection and asynchrous calls, the service life must be changed to transient. – Ashraf Sada May 22 '23 at 02:24
178

In some cases, this error occurs when calling an async method without the await keyword, which can simply be solved by adding await before the method call. however, the answer might not be related to the mentioned question but it can help solving a similar error.

Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
Hamid Nasirloo
  • 2,074
  • 2
  • 11
  • 12
  • 16
    This happened to me. Changing `First()` to `await / FirstAsync()` worked. – Guilherme May 30 '19 at 22:57
  • 1
    Upvote. Also see https://jimlynn.wordpress.com/2017/11/16/entity-framework-error-a-second-operation-started-on-this-context-before-a-previous-operation-completed-any-instance-members-are-not-guaranteed-to-be-thread-safe/ Jim Lynn "ENTITY FRAMEWORK ERROR: A SECOND OPERATION STARTED ON THIS CONTEXT BEFORE A PREVIOUS OPERATION COMPLETED. ANY INSTANCE MEMBERS ARE NOT GUARANTEED TO BE THREAD SAFE." – granadaCoder Feb 14 '20 at 16:51
  • 1
    Thanks for this! This was exactly my problem...Forgot to add an await on an async mdethod. – AxleWack Mar 26 '20 at 11:01
  • Happened to me as well, and this comment helped as I've searched where I forgot a missing await. Once I found it, problem's solved. – Zion Hai Apr 25 '20 at 20:27
  • with the Resharper "Errors/Warnings window" you can quickly search globally for such problems – gabriel211 Feb 19 '21 at 10:11
  • En mi caso un metodo asincrono devolvia "void" debi agregar un tipo de respuesta – Antonio Guerrero Mar 15 '21 at 02:25
  • 1
    OMG! You are a legend! It took me way too long to find this – André Brink Apr 01 '22 at 08:10
  • This happened to me as well, I had a series of async statements and had missed await in one of them – raw_hitt Nov 04 '22 at 00:25
  • Thanks a lot, I published MediatR notification without await... – cryss Feb 23 '23 at 20:47
  • You are a hero, sir. I literally had ONE method I forgot to add async/await to... – John Edwards Mar 06 '23 at 23:39
53

The exception means that _context is being used by two threads at the same time; either two threads in the same request, or by two requests.

Is your _context declared static maybe? It should not be.

Or are you calling GetClients multiple times in the same request from somewhere else in your code?

You may already be doing this, but ideally, you'd be using dependency injection for your DbContext, which means you'll be using AddDbContext() in your Startup.cs, and your controller constructor will look something like this:

private readonly MyDbContext _context; //not static

public MyController(MyDbContext context) {
    _context = context;
}

If your code is not like this, show us and maybe we can help further.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • 1
    Probably it's the job I have. I managed to solve, see my answer. But I'm marking yours as the right one – André Luiz Feb 13 '18 at 13:53
  • @NMathur Are you using your `_context` object in other threads? Like inside a `Task.Run()` for example? – Gabriel Luci Sep 21 '20 at 12:54
  • @NMathur That looks fine. Just make sure you're always using `await` with async methods. If you don't use `await`, you can unintentionally get into multi-threading. – Gabriel Luci Sep 22 '20 at 13:14
34

I had the same problem and it turned out that parent service was a singelton. So the context automatically became singelton too. Even though was declared as Per Life Time Scoped in DI.

Injecting service with different lifetimes into another

  1. Never inject Scoped & Transient services into Singleton service. ( This effectively converts the transient or scoped service into the singleton. )

  2. Never inject Transient services into scoped service ( This converts the transient service into the scoped. )

DiPix
  • 5,755
  • 15
  • 61
  • 108
  • 1
    this was my issue exactly – Jonesopolis Oct 28 '20 at 18:56
  • This was my problem too. I was registering a handler class as singleton, and the DbContext as transient. I had to use ServiceProvider within the Handler class to get a transient instance from the DI container everytime the Handler is hitted – Daiana Sodré Nov 05 '20 at 19:17
  • This is a very concise explanation of the most common scenario where you will experience this exception, especially if you are just starting out. – Chris Schaller Jul 29 '21 at 06:21
  • My Parent service was a scoped service, and I was injecting a transient service into it. After making it Transient too, it works fine. But I still need to learn the implications of having transient services. Thank you! – OlatunjiYSO Oct 02 '21 at 06:28
  • I was registering a singleton factory that returned scoped instances. Made the factory scoped as well and the issue was gone. – Jorn.Beyers Dec 30 '21 at 10:43
29
  • Solve my problem using this line of code in my Startup.cs file.
    Adding a transient service means that each time the service is requested, a new instance is created when you are working with Dependency injection

           services.AddDbContext<Context>(options =>
                            options.UseSqlServer(_configuration.GetConnectionString("ContextConn")),
                 ServiceLifetime.Transient);
    
19

I had the same error. It happened because I called a method that was constructed as public async void ... instead of public async Task ....

Vahid Farahmandian
  • 6,081
  • 7
  • 42
  • 62
Raynlaze
  • 201
  • 2
  • 4
19

I think this answer still can help some one and save many times. I solved a similar issue by changing IQueryable to List(or to array, collection...).

For example:

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

to

var list = _context.table1.Where(...).ToList(); //or ToArray()...
ˈvɔlə
  • 9,204
  • 10
  • 63
  • 89
Makhidi Masimov
  • 327
  • 2
  • 5
  • 7
    IMHO, This answer does not deserve minus points, It is just poorly expressed. .ToList() indeed solves the majority of the problems "a second operation..." due to the fact that it forces the immediate evaluation of the expression. This way there are no queueing context operations. – vassilag Feb 06 '20 at 11:03
  • 3
    This was the issue in my case. I had xxx.Contains(z.prop) in a where clause of a query. xxx was supposed to be a distinct int[] array resolved from an earlier query. Unfortunately, by the time the second query hit, xxx was still an IQueryable. Adding xxx.ToArray() prior to the second query fixed my issue. – Jason Butera Aug 24 '20 at 13:55
11

Entity Framework Core does not support multiple parallel operations being run on the same DbContext instance. This includes both parallel execution of async queries and any explicit concurrent use from multiple threads. Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute in parallel.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Ehsan Gondal
  • 305
  • 4
  • 8
  • That was the case for me. I finally found that I forgot to call await after a FirstOrDefaultAsync(..) call. It is strange that it works in Debug mode but fail in release mode. – NthDeveloper Jul 07 '21 at 09:11
7

I have a background service that performs an action for each entry in a table. The problem is, that if I iterate over and modify some data all on the same instance of the DbContext this error occurs.

One solution, as mentioned in this thread is to change the DbContext's lifetime to transient by defining it like

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

but because I do changes in multiple different services and commit them at once using the SaveChanges() method this solution doesnt work in my case.

Because my code runs in a service, I was doing something like

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);
   } 
}

to be able to use the service like if it was a simple request. So to solve the issue i just split the single scope into two, one for the query and the other for the write operations like so:

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);
   } 
}

Like that, there are effevtively two different instances of the DbContext being used.

Another possible solution would be to make sure, that the read operation has terminated before starting the iteration. That is not very pratical in my case because there could be a lot of results that would all need to be loaded into memory for the operation which I tried to avoid by using a Queryable in the first place.

NiPfi
  • 1,710
  • 3
  • 18
  • 28
4

I faced the same issue but the reason was none of the ones listed above. I created a task, created a scope inside the task and asked the container to obtain a service. That worked fine but then I used a second service inside the task and I forgot to also asked for it to the new scope. Because of that, the 2nd service was using a DbContext that was already disposed.

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.
            ...
        }
    }

I should have done this:

var otherProductService = scope.ServiceProvider.GetService<IProductService>();
otherProductService.DoSomething();
Aranha
  • 2,903
  • 3
  • 14
  • 29
Francisco Goldenstein
  • 13,299
  • 7
  • 58
  • 74
  • Wouldn't the context only be exposed once everything in the using block has completed execution? – Sello Mkantjwa Jan 14 '19 at 08:17
  • When the action is disposed, everything is disposed in that scope. If you have a task running in the background and that task is longer that the action, you will have this issue unless you create a new scope for the task, just like I did in the example. On the other hand, if your task could take long time or you want to be 100% sure that it will run, you might need to use a queue. If you are using Azure, you could use Service Bus queues. – Francisco Goldenstein Jan 14 '19 at 13:34
4

Adding another possible resolution to this error, in case it helps someone.

In my case the issue was using a nav property inside a query, like this:

var selectedOrder = dbContext.Orders.Where(x => x.Id == id).Single();
var relatedOrders = dbContext.Orders.Where(x => x.User.Id == selectedOrder.User.Id).ToList();

The issue is using selectedOrder.User.Id in the query. If the User nav property hasn't already been loaded, EF will try to lazy load the property in the middle of trying to execute the query, which it sees as trying to start a second operation. The solution was to make a separate variable for selectedOrder.User.Id, to make sure the information needed for the query was loaded before the query started:

var selectedOrder = dbContext.Orders.Where(x => x.Id == id).Single();
var userId = selectedOrder.User.Id;
var relatedOrders = dbContext.Orders.Where(x => x.User.Id == userId).ToList();
Kestrel12
  • 116
  • 7
3

This error might also appear if you created the migration (Add-Migration) but forgot to actually update the database (Update-Database).

Luis Gouveia
  • 8,334
  • 9
  • 46
  • 68
3

There is some function Async but you don't call await before it. Find and add await before calling that function will help you.

Lê Văn Hiếu
  • 171
  • 1
  • 6
2

I know this issue has been asked two years ago, but I just had this issue and the fix I used really helped.

If you are doing two queries with the same Context - you might need to remove the AsNoTracking. If you do use AsNoTracking you are creating a new data-reader for each read. Two data readers cannot read the same data.

  • Good pickup on the `DataReader` but there are other exceptions that can occur, this suggestion is still only a work around. You should not be trying to perform two queries at the same time against the same context, just create a new isolated context if you need it. – Chris Schaller Jul 29 '21 at 06:27
1

put the following code in your .csproject file and correct all errors

  <PropertyGroup>
     <WarningsAsErrors>CS4014</WarningsAsErrors>
  </PropertyGroup>

this code force you to use await for async methods

msn.secret
  • 419
  • 5
  • 11
  • Really! I thought that I had correct calls everywhere. But thanks to this setting, I found an incorrect call – ShurikEv Apr 20 '23 at 06:18
0

I got the same message. But it's not making any sense in my case. My issue is I used a "NotMapped" property by mistake. It probably only means an error of Linq syntax or model class in some cases. The error message seems misleading. The original meaning of this message is you can't call async on same dbcontext more than once in the same request.

[NotMapped]
public int PostId { get; set; }
public virtual Post Post { get; set; }

You can check this link for detail, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed

Sharon Zhou
  • 61
  • 1
  • 4
0

I managed to get that error by passing an IQueryable into a method that then used that IQueryable 'list' as part of a another query to the same context.

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();
}

To stop that happening I used the approach here and materialised that list before passing it the second method, by changing the call to SecondMethod to be SecondMethod(stockItems.ToList()

tomRedox
  • 28,092
  • 24
  • 117
  • 154
0

First, upvote (at the least) alsami's answer. That got me on the right path.

But for those of you doing IoC, here is a little bit of a deeper dive.

My error (same as others)

One or more errors occurred. (A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.)

My code setup. "Just the basics"...

public class MyCoolDbContext: DbContext{
    public DbSet <MySpecialObject> MySpecialObjects {        get;        set;    }
}

and

public interface IMySpecialObjectDomainData{}

and (note MyCoolDbContext is being injected)

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);
    }
}

and

public interface IMySpecialObjectManager{}

and

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);
    }
}

And finally , my multi threaded class, being called from a Console App(Command Line Interface app)

    public interface IMySpecialObjectThatSpawnsThreads{}

and

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);
    }
}

and the DI buildup. (Again, this is for a console application (command line interface)...which exhibits slight different behavior than web-apps)

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;
}

The ones that surprised me were the (change to) transient for

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

Note, I think because IMySpecialObjectManager was being injected into "MySpecialObjectThatSpawnsThreads", those injected objects needed to be Transient to complete the chain.

The point being.......it wasn't just the (My)DbContext that needed .Transient...but a bigger chunk of the DI Graph.

Debugging Tip:

This line:

this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);

Put your debugger break point there. If your MySpecialObjectThatSpawnsThreads is making N number of threads (say 10 threads for example)......and that line is only being hit once...that's your issue. Your DbContext is crossing threads.

BONUS:

I would suggest reading this below url/article (oldie but goodie) about the differences web-apps and console-apps

https://mehdi.me/ambient-dbcontext-in-ef6/

Here is the header of the article in case the link changes.

MANAGING DBCONTEXT THE RIGHT WAY WITH ENTITY FRAMEWORK 6: AN IN-DEPTH GUIDE Mehdi El Gueddari

I hit this issue with WorkFlowCore https://github.com/danielgerlag/workflow-core

  <ItemGroup>
    <PackageReference Include="WorkflowCore" Version="3.1.5" />
  </ItemGroup>

sample code below.. to help future internet searchers

 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));

            }
        }
    }

and

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);
                }
granadaCoder
  • 26,328
  • 10
  • 113
  • 146
-5

If your method is returning something back, you can solve this error by putting .Result to the end of the job and .Wait() if it doesn't return anything.

Derek C.
  • 890
  • 8
  • 22
Hasan_H
  • 77
  • 4
-6

you can use SemaphoreSlim to block the next thread that will try to execute that EF call.

static SemaphoreSlim semSlim = new SemaphoreSlim(1, 1);

await semSlim.WaitAsync();
try
{
  // something like this here...
  // EmployeeService.GetList(); or...
  var result = await _ctx.Employees.ToListAsync();
}
finally
{
  semSlim.Release();
}
aspnetcoreguy
  • 135
  • 2
  • 12
  • 2
    Please, no. It's *never* a good idea to share a context instance with multiple threads even when in serial processing. – Gert Arnold Jan 11 '21 at 08:07