Multiple concurrent queries in EF Core
I recently had to convince someone that the Entity Framework Core DbContext
doesn't support running multiple queries in parallel even if the underlying connection does. Since I couldn't immediately find the documentation proving my point, I created a sample project which I decided to polish a little and share publicly.
I created a contrived test which makes multiple asynchronous queries in parallel without awaiting them individually using LINQ:
[TestCase(false)]
[TestCase(true)]
public async Task ParallelQueriesWithSingleDbContext(bool multipleActiveResultSets)
{
using var dbContext = CreateDbContext(multipleActiveResultSets);
var action = async () =>
await Task.WhenAll(
usernames.Select(username =>
dbContext.Users.SingleOrDefaultAsync(user => user.Username == username)
)
);
await action.Should().ThrowAsync<InvalidOperationException>();
}
The CreateDbContext
method creates a new DbContext
with multiple active result sets enabled or disabled in the connection string depending on the input parameter:
private SampleDbContext CreateDbContext(bool multipleActiveResultSets)
{
var connectionStringBuilder = new SqlConnectionStringBuilder(
msSqlContainer.GetConnectionString()
)
{
InitialCatalog = databaseName,
MultipleActiveResultSets = multipleActiveResultSets
};
var optionsBuilder = new DbContextOptionsBuilder<SampleDbContext>().UseSqlServer(
msSqlContainer.GetConnectionString()
);
return new SampleDbContext(optionsBuilder.Options);
}
The test passes in both cases, meaning that an InvalidOperationException
was thrown even when the database connection allowed multiple active statements because multiple active result sets were enabled:
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 ofDbContext
. For more information on how to avoid threading issues withDbContext
, see https://go.microsoft.com/fwlink/?linkid=2097913.
The link in the error message points at the documentation page which makes it completely clear that a single DbContext
instance doesn't support running multiple queries in parallel:
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, alwaysawait
async calls immediately, or use separateDbContext
instances for operations that execute in parallel.
If you really need to do this (unlike me in my contrived example), you need to use a separate DbContext
instance for each query:
[TestCase(false)]
[TestCase(true)]
public async Task ParallelQueriesWithMultipleDbContexts(bool multipleActiveResultSets)
{
var users = await Task.WhenAll(
usernames.Select(async username =>
{
using var dbContext = CreateDbContext(multipleActiveResultSets);
return await dbContext.Users.SingleOrDefaultAsync(user =>
user.Username == username
);
})
);
users.Should().HaveCount(2);
users.Should().NotContainNulls();
}
I'm creating instances with explicit constructor calls in my tests. In a real application, you will typically be using the single DbContext
instance injected into your class. To be able to create multiple instances, you need to register with dependency injection and inject DbContextFactory
instead.
You can find full sample code in my GitHub repository. I'm using Testcontainers to set up Microsoft SQL Server on the fly in Docker and shut it down immediately after running the tests.
DbContext
in Entity Framework Core is not thread safe and as such doesn't support running multiple queries in parallel. If you need to do that, you have to create a separate DbContext
for each query.