4

I search thru all the links which had solution for the error. But, none of them were applicable for me as I had asynchronous code already and doing everything they have suggested.

We have Azure Functions based on .NET Core 3.1. We use latest version of Entity Framework Core. We are intermittently getting this error:

System.InvalidOperationException: The connection does not support MultipleActiveResultSets.

at Microsoft.Data.SqlClient.SqlCommand.<>c.b__164_0(Task1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
End of stack trace from previous location where exception was thrown

at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
End of stack trace from previous location where exception was thrown ---

at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func4 operation, Func4 verifySucceeded, TState state, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func4 operation, Func4 verifySucceeded, TState state, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.MoveNextAsync() at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)

When we looked at the logs in AppInsights, we found that the exceptions occurred at the same time with exact same error for same function at the same place. But, it was for three different invocations (InvocationId) but same host instance (HostInstanceId) and different operation Id (Operation ID). Expectation is that for every new invocation, new dbContext will be instantiated as the AddDbContextPool adds scoped dbContext as dependency by default. Not sure if we can deduct anything out of it.

Below is our implementation approach. Appreciate any help on this. Thanking in advance.

We add DbContext to the services using following statement in the startup file:

builder.Services.AddDbContextPool<OurDbContext>(options =>
{
    options.UseSqlServer("connectionstring"), builder =>
    {
       builder.EnableRetryOnFailure(3, TimeSpan.FromSeconds(2), null);
    });
});

OurDbContext class has the following constructor:

public OurDbContext(DbContextOptions<OurDbContext> options)
    : base(options)
{
}

And then we inject OurDbContext class in different repositories which uses this context to talk to SQL. Similar to below:

public class TypesRepo : RepoBase<Types>, ITypesRepo
{
  public TypesRepo(OurDbContext ourDbContext) : base(ourDbContext)
  {
  }
  
  public async Task RetrieveTypesAsync(List<string> types)
  {
    var records = await RetrieveConditionAsync(x => types.Contains(x.Type));
    return records?.Select(x => new { x.Type, x.TypeId })
                   .ToDictionary(x => x.Type, x => x.TypeId);
  }
}

public abstract class RepoBase<T> where T : class
{
    protected OurDbContext OurDbContext  { get; set; }

    public RepoBase(OurDbContext OurDbContext)
    {
        this.OurDbContext = OurDbContext;
    }

    public async Task<List<T>> RetrieveConditionAsync(Expression<Func<T, bool>> expression)
    {
        return await OurDbContext.Set<T>().Where(expression).AsNoTracking().ToListAsync();
    }
}

We inject above Repo class in Function classes and call above methods such as

await _typesRepo.RetrieveAsync()

P.S.: Based on below comment
I think dbcontextpool will reuse the dbcontext instance if it the connection is not active/unused but not the one which is active.

AddDbContext or AddDbContextPool

Vicky
  • 624
  • 2
  • 12
  • 35
  • Do you use any ContinueWith in your code? and are you using System.Data.SqlClient nuget, if so which version? I have done the same using a different approach where I have an ActivityTrigger that executes the SQL logic using procedures for a very big number of executions/day – Bassam Gamal Oct 06 '20 at 06:45
  • No we are not using ContinueWith at all in our code. Neither System.Data.SqlClient nuget. We use Entity framework core 3.1.7 which in turn uses Microsoft.Data.SqlClient 1.1.3. – Vicky Oct 06 '20 at 13:06
  • Have you tested adding `MultipleActiveRecordSets=true;` to the connection string? It will help you to narrow down the issue, even if you don't want to use it permanently. – atiyar Oct 06 '20 at 16:41
  • 1
    Does the exception always disappear if you turn off context pooling? And is lazy loading involved? – Gert Arnold Oct 11 '20 at 07:47
  • It occurs intermittently when it is executed by different requests at the same time. No lazy loading involved. Ctrl c+v from my question, --------------When we looked at the logs in AppInsights,we found that the exceptions occurred at the same time with exact same error for same function at the same place. But,it was for three different invocations (InvocationId) but same host instance (HostInstanceId) and different operation Id (Operation ID).Expectation is that for every new invocation, new dbContext will be instantiated as the AddDbContextPool adds scoped dbContext as dependency by default. – Vicky Oct 13 '20 at 15:23

2 Answers2

1

Your connection string needs to specify MultipleActiveRecordSets=true; to tell SqlServer to make sure this feature is enabled on the connections you make to it.

For example:

Data Source=localhost;Initial Catalog=master;Integrated Security=True;MultipleActiveResultSets=True

More info here: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/enabling-multiple-active-result-sets

Jason Holloway
  • 682
  • 5
  • 9
  • Yeah I read that in multiple places but at the same time it says there is a performance hit if you enable MARS. And in this case since these are three different dbcontext instances, each instance has just one result set from one query. Not sure why it is throwing this error. – Vicky Oct 01 '20 at 23:26
  • Also I am not sure why we would we need MARS enabled in this case? – Vicky Oct 01 '20 at 23:40
  • This is one of the great benefits of Entity Framework: transparency – Jason Holloway Oct 01 '20 at 23:46
  • 1
    I think I understand your problem more now - the entire point of `AddDbContextPool` is to share DbContexts and connections between requests. But from what you're seeing, it looks like something is still active on the connection when the DbContext gets passed on, hence the MARS complaint from the server – Jason Holloway Oct 02 '20 at 00:05
  • or - the DbContexts are sharing connections, which means interleaved sql commands – Jason Holloway Oct 02 '20 at 00:15
  • I think dbcontextpool will reuse the dbcontext instance if it is unused but not the one which is active. https://stackoverflow.com/a/48444206/5887074 – Vicky Oct 02 '20 at 12:26
  • If you're receiving `Keyword not supported: 'multipleactiverecordsets'` exception, then use `MultipleActiveResultSets` option. – pizycki Dec 23 '22 at 11:05
1

We recently ran into a similar issue, and it was due to having a request spin up multiple threads simultaneously, and each thread was using the same DbContext to parallelize a heavy database query.

Further, we use DbReader for working with the results, and that locks up the connection until the reader is disposed of.

That prevents the other threads, which are re-using the same DbContext / connection instance, which also are using DbReader to process results.

Enabling the multiple-active result sets feature solves this for us, and in our case, we actually do not notice a performance hit at all with this.

ajawad987
  • 4,439
  • 2
  • 28
  • 45