How to use DbContext in multi-threaded app

1

I have an EF Core website that is set up in the typical way. This has worked fine.

However, I added a Controller Method that gets hit several times per second and when there are multiple active threads, EF throws an exception. If the requests are a few seconds apart, then it works fine.

I have tried debugging this, but I don't see what I am doing wrong.

Here are the details:

The requests are coming from a single page app, and they are all the same connection. If there is only one request, it works fine. But if there are two requests, then the exception is thrown.

System.IndexOutOfRangeException HResult=0x80131508 Message=Index was outside the bounds of the array. Source=System.Data StackTrace: at System.Data.SqlClient.SqlDataReader.CheckHeaderIsReady(Int32 columnIndex, Boolean permitAsync, String methodName) at System.Data.SqlClient.SqlDataReader.IsDBNull(Int32 i) at Microsoft.EntityFrameworkCore.Storage.Internal.TypedRelationalValueBufferFactory.Create(DbDataReader dataReader) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable1.AsyncEnumerator.<BufferlessMoveNext>d__12.MoveNext() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.<ExecuteImplementationAsync>d__312.MoveNext() at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.d__312.MoveNext() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable1.AsyncEnumerator.d__11.MoveNext() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor1.EnumeratorExceptionInterceptor.<MoveNext>d__5.MoveNext() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Linq.AsyncEnumerable.<Aggregate_>d__63.MoveNext() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at Traken.Data.Repositories.LookupListItemRepository.d__4.MoveNext() in C:\Dev\Traken.app\Traken.5\Dev\Traken\Data\Repositories\LookupListItemRepository.cs:line 38

The repository which is use:

services.AddScoped<LookupListItemRepository, LookupListItemRepository>();

I have also tried AddTransient but I get the same error.

services.AddTransient<LookupListItemRepository, LookupListItemRepository>();

The DbContext:

services.AddDbContext<ApplicationDbContext>(optionsBuilder =>
{
                optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
options => options.EnableRetryOnFailure());
});

The controller

[HttpGet("{tableName}")]
public async Task<List<LookupListItem>> GetLookupTableAsync(string tableName) => await this._repository.List(tableName);

The Repository

public async Task<List<LookupListItem>> List(string tableName)
{
    return await this._dbContext.LookupTables.FromSql(
                    $@"{SqlStrings.SP.GetLookupTable} 
                    @tableName = {tableName}"
                ).AsNoTracking().ToListAsync();
}

How do I resolve this?

entity-framework
entity-framework-core
entity-framework-core-2.1
asked on Stack Overflow Dec 22, 2018 by Greg Gum

1 Answer

1

I resolved this by setting the repository to AddTransient, instead of AddScoped

services.AddTransient<LookupListItemRepository, LookupListItemRepository>();

and setting the lifetime of the context to Transient.

services.AddDbContext<ApplicationDbContext>(optionsBuilder =>
{   optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
        options => options.EnableRetryOnFailure());
}, ServiceLifetime.Transient);

See Also: https://docs.microsoft.com/en-us/ef/core/querying/async

public async Task<List<Blog>> GetBlogsAsync()
{
    using (var context = new BloggingContext())
    {
        return await context.Blogs.ToListAsync();
    }
}
answered on Stack Overflow Dec 22, 2018 by Greg Gum • edited Dec 22, 2018 by Greg Gum

User contributions licensed under CC BY-SA 3.0