MarimerLLC / cslaforum

Discussion forum for CSLA .NET
https://cslanet.com
Other
31 stars 6 forks source link

Entity Framework DbContext is same instance in different threads #881

Open michaelcsikos opened 4 years ago

michaelcsikos commented 4 years ago

Question In a new application I'm getting the following exception:

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', however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.

For years, I have fired off async fetch requests in different parts of the UI simultaneously, and this has never been an issue. In my old apps, I can't reproduce it, but in the new app it happens every time.

To test it, inside the using (var ctx = DbContextManager<EfDbContext>.GetManager()) I compare the DbContext to see if the new one is the same instance as another one that's already been created. I'm firing off a few async requests at once, e.g.

_ = WidgetList.GetListAsync();
_ = WidgetList.GetListAsync();
_ = WidgetList.GetListAsync();

In the new app, the DbContext is sometimes being shared, and the exception is thrown. I thought the DbContextManager should only share the DbContext "vertically", i.e. within a save operation, the children use the same context as the parent, not "horizontally", where different threads are fetching data.

The exe and WindowsForms assemblies are full .NET; the Business, Dal, DalEf assemblies are NetStandard 2.0 with EF Core. The old app is .NET 4.7.2 with EF Core 2.2.6; the new app is .NET 4.8 with EF 3.1.2.

I've tried rolling back versions of EF NuGet packages to match the older projects, but it's still happening. I'm stumped.

Version and Platform CSLA version: 4.11.2 Platform: WinForms

rockfordlhotka commented 4 years ago

DbContextManager relies on the CSLA LocalContext to get isolation.

LocalContext defaults to using AsyncLocal to achieve that isolation. This is a change from earlier versions of CSLA where thread local storage (TLS) was used.

In CSLA 5.1 you can configure your app to use the old TLS behavior because I added /ApplicationContextManagerTls as a type in Csla.Core.

You can just copy-paste that code into your project though, and use it in 4.11 too.

Just configure ApplicationContext.ContextManager (?) on app startup.

rockfordlhotka commented 4 years ago

That said, I don't know why AsyncLocal wouldn't work - it is supposed to maintain per-thread and per-context state.

And maybe that last bit is the problem? Maybe because the code is all running on the client, it is carrying the UI context through every thread, and so they are all treated as being part of a single context? Hmm...

If that's true, then I should change the Windows Forms default behavior back to using TLS.

michaelcsikos commented 4 years ago

Thanks for your replies, Rocky. I've tried with the ApplicationContextManagerTls and now it's throwing, "Cannot access a disposed object." I noticed in another solution I've used Csla.Windows.ApplicationContextManager but that gives me the original "A second operation" exception.

I'll try to make a bare-bones sample that reproduces this. Maybe I'm doing something silly, or there's something funny with EF Core 3.1.2.

rockfordlhotka commented 4 years ago

It is also the case that if you are using CSLA 5.1 you can avoid the use of the CSLA db context manager types, and instead switch to dependency injection of the EF context directly.