-1

I'm migrating from netcoreapp 2.1 to 3.1, and I've found a breaking change for EF core 3.1 that I can't resolve. The following worked in 2.1 so simply saying db context isn't thread safe by design and pointing to other questions that don't deal with this nuance does not address the issue at hand.

Previously in 2.1, this worked:

taskList.Add(MethodOne(myRequestObject));
taskList.Add(MethodTwo(myRequestObject));

await Task.WhenAll(taskList);

where both methods only read(never changed) from the db context and look something like this:

private async Task MethodOne(RequestObject myRequestObject)
{
    var entity = await DbContext
    .MyDbSet
    .OrderByDescending(x => x.SomeProperty)
    .FirstOrDefaultAsync(x => x.Id == myRequestObject.Id);

    if (entity != null)
        myRequestObject.SomeRequestProperty = entity.AnotherProperty;
    }
}

In 3.1, even when I'm just reading and not changing the entities, the DB context ConcurrencyDetector thinks it has EnterCriticalSection which causes an exception when the second method tries to await on the DbContext:

InvalidOperationException: A second operation started on this context before a previous operation completed

For baseline sanity, I also tried the following which does work(but is not an ideal solution for the real code of my app):

await MethodOne(myRequestObject);
await MethodTwo(myRequestObject);

So here is my questions:

  1. Is there a way to continue to tell EF core 3.1 to allow concurrency when I know it is safe. this worked in 2.1 so clearly this cannot be dismissed by simply saying db context has never allowed this by design and closing the question as a duplicate. My app has been running happily in production for a long time. Only after migrating to 3.1 has this become an issue. What has changed? Is this change truly impossible to work around or are there exceptions to the claim that the context doesn't allow this? If this worked before when this 'wasn't allowed by design', is it possible that it is similarly not true now as well?
Julian
  • 33,915
  • 22
  • 119
  • 174
  • 2
    Actually it can all be said because this is a documented (in issues) design change done by the people in charge of efcore, and like so many it is not thought out, noone cares about consequences and you (and me and others) are left with the results. – TomTom Mar 06 '20 at 18:59
  • 3
    Please do not re-ask a question closed as a duplicate. Instead, [edit] your existing question to include your reasoning (preferably not in all-caps or all-bold formatting) as to why it is not a duplicate. This is covered in [the help center article on duplicates](https://stackoverflow.com/help/duplicates) for reference. – Heretic Monkey Mar 06 '20 at 19:01
  • 5
    Reposted from ["A second operation started on this context" EF core 3.1 concurrency breaking change](https://stackoverflow.com/questions/60569523/a-second-operation-started-on-this-context-ef-core-3-1-concurrency-breaking-ch) – Heretic Monkey Mar 06 '20 at 19:02
  • 1
    So simply telling the truth isn't acceptable? Good luck with that. Contexts have never been thread-safe. It doesn't always show, is all. EF core 3 helps you better in avoiding dangerous code. – Gert Arnold Mar 06 '20 at 21:55
  • @HereticMonkey That was NOT what I was instructed to do when my question was originally closed as a duplicate. I was specifically instructed to create a new question and my original was marked as a duplicate(which is wasn't for the reasons I've stated). I don't appreciate being given impossible and contradictory instructions, being accused of claiming 'telling the truth isn't acceptable' when in fact I am requesting more precision in the answers (or lack of answers I was given before dismissing my question) and all the other bureaucratic hurtles raised against this legitimate question. – Brad Sherard Mar 11 '20 at 16:02
  • I'm sorry you are finding the instructions contradictory. I also find the instructions occasionally unclear, especially as Stack Overflow (the company) makes changes to the close notices that directly contradict what's written in the help center and long-standing FAQs. I'm not sure how the instructions are impossible; you have the ability to edit your own questions. I never accused you of any claims, so I'm not sure why that was addressed to me. Again, simply editing your previous question would have been sufficient to clear all "bureaucratic hurdles". – Heretic Monkey Mar 11 '20 at 16:12

2 Answers2

2

Is there a way to continue to tell EF core 3.1 to allow concurrency when I know it is safe. this worked in 2.1 so clearly this cannot be dismissed by simply saying db context has never allowed this by design and closing the question as a duplicate.

The DB context has never allowed that. I've never seen this kind of code work on any version of .NET Core. If it happened to work in 2.x, it was only "working" in the sense that the code was winning its race conditions and thus just getting lucky. Any change in performance - reduced memory, antivirus software, alternate network routing - could have caused this to fail. It's very important to recognize that the code is wrong and always has been.

My app has been running happily in production for a long time. Only after migrating to 3.1 has this become an issue. What has changed?

Probably some timing in the framework.

If this worked before when this 'wasn't allowed by design', is it possible that it is similarly not true now as well?

The race condition still exists. If your code happens to win the race condition, you won't see that exception.

Is this change truly impossible to work around or are there exceptions to the claim that the context doesn't allow this?

There's no workaround to force a dbcontext to work with multiple simultaneous requests. The normal pattern for doing simultaneous requests is to use multiple dbcontexts, one for each simultaneous request.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

A DbContext is cheap to create. By default, if you're injecting them into a scoped service, a new one is created for every request. But you can also create them yourself, while still leveraging .NET Core's DI framework.

Declare your DbContext in Startup.cs as you normally would. But the service (you are injecting services into your controllers, right?) that uses your DbContext can be a singleton instead of scoped, since it doesn't need to be injected with a new DbContext per-request. The background task started by the service can then use a ScopeFactory to create contexts as needed:

using (var scope = ScopeFactory.CreateScope())
using (var db = scope.ServiceProvider.GetRequiredService<MyDbContext>())
{
    // do stuff with db
}
StackOverthrow
  • 1,158
  • 11
  • 23