Reputation: 4405
I have developed a multithreaded application that uses DbContext of EF Core.
At some point I am receiving this error:
System.InvalidOperationException: 'A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently 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 .'
I am instantiating DbContext
and injecting it to services constructors using dependency injection.
This is how I am adding the services in Program.cs file:
var serviceCollection = new ServiceCollection();
var serviceProvider = serviceCollection
.AddSingleton<IGymService, GymService>()
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<ICommService, CommService>()
.AddSingleton<IProtocolService, ProtocolService>()
.AddDbContext<MyLocalContext>(options =>
{
options.UseSqlServer(settings.DbConnection, o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
})
.BuildServiceProvider();
At first, I thought it was due because all services are singleton, however, I have tried with AddScoped and AddTransient as well, but the same occurs.
How can I get rid of this problem? Maybe by opening the connection every time I use it but I don't know how to do it.
Also I thought to use using (var db = new MyLocalContext(...)) { }
but I don't know if this a good solution. The link the error message points to does not give any solution. It only explains the problem.
Any hint?
Upvotes: 1
Views: 67
Reputation: 636
In EntityFramework, the DbContext is not thread-safe. This error is caused by the DbContext being access from multiple threads at the same time. To fix this utilize the IDbContextFactory
and create a dedicated scope inside of each thread/task. Below is a code example and Microsoft doc.
Setup dependency injection:
var serviceCollection = new ServiceCollection();
var serviceProvider = serviceCollection
.AddSingleton<IGymService, GymService>()
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<ICommService, CommService>()
.AddSingleton<IProtocolService, ProtocolService>()
.AddDbContext<MyLocalContext>(options =>
{
options.UseSqlServer(settings.DbConnection, o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
})
// Setup factory here and add lifetime if necessary
.AddDbContextFactory<MyLocalContext>()
.BuildServiceProvider();
Inject into constructors as:
IDbContextFactory<MyLocalContext>
Usage:
using (var dbContext = await _dbContextFactory.CreateDbContextAsync())
{
// Do Stuff here
}
Optional non-async usage:
using (var dbContext = _dbContextFactory.CreateDbContext())
{
// Do Stuff here
}
Microsoft Documentation: https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#use-a-dbcontext-factory
Upvotes: 6
Reputation: 24280
If this happens, you can try adding ;MultipleActiveResultSets=true
to your Connection String. This may add some overhead to all operations, but so far (at least in my experience) the effect has been negligible on a busy production website.
This feature is also known as MARS
and it is supported by SQL Server 2005 and above.
Note that not all database providers may support it, usually this will be documented somewhere.
If it is not supported by the provider then your only options are: (1)
making sure that every operation is fully completed and all data loaded before the next operation is started (on the same DbContext), or (2)
using a fresh DbContext where needed or all the time.
Upvotes: 2