Efcore: AddDbContextPool in asp.net core System.InvalidOperationException

Created on 9 Sep 2017  路  10Comments  路  Source: dotnet/efcore

I'm using db context pooling in a Asp.Net core MVC application.
When I launch multiple simultaneous requests I get an error :

Exception message: System.InvalidOperationException occurred in Microsoft.EntityFramework.Core.dll
A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

or 
Exception message: System.ObjectDisposedException: 'Cannot access a disposed object.

code;
IQueryable<TEntity> query = DbSet;
            if (filter != null)
                query = query.Where(filter); // exception occurs here

Maybe I'm not using DbContext pooling the right way...

Steps to reproduce

I launch mutiple requests in browser to the same web api controller method, which retrieves some data from the database.

```c#
// Dependency injection is configured in Startup as follows
services.AddScoped(provider => provider.GetService());

// if I use service.AddScoped();
// I get the impression db context pooling is not working,
// but I don't get the exception message either.

// db context pooling in Startup class
services.AddDbContextPool(options =>
{
string connectionString = Configuration.GetConnectionString("MyConnection");
options.UseSqlServer(connectionString);
},10);
```

Further technical details

EF Core version:Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0"
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Win10
IDE: Visual Studio 2017

closed-fixed type-bug

Most helpful comment

@rekna1 I was actually able to reproduce this in a console application, so no need to dig further.

Note for triage: Works fine when AddDbContextPool is replaced by AddDbContext

```C#
public interface IMyDbContext
{
DbSet Foos { get; set; }
}

public class MyDbContext : DbContext, IMyDbContext
{
public MyDbContext(DbContextOptions options)
: base(options)
{
}

public DbSet<Foo> Foos { get; set; }

}

public class Foo
{
public int Id { get; set; }
}

public class Program
{
public static void Main()
{
var services = new ServiceCollection()
.AddScoped(provider => provider.GetService())
.AddDbContextPool(options =>
{
options.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;ConnectRetryCount=0");
})
.BuildServiceProvider();

    using (var scope = services.CreateScope())
    {
        var context = (DbContext)scope.ServiceProvider.GetService<IMyDbContext>();
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();
    }

    Parallel.For(0, 100, s =>
    {
        using (var scope = services.CreateScope())
        {
            var context = scope.ServiceProvider.GetService<IMyDbContext>();

            Console.WriteLine("Using " + context.GetHashCode());
            context.Foos.Add(new Foo());
            ((DbContext) context).SaveChanges();
            var _ = context.Foos.ToList();
        }
    });
}

}
```

All 10 comments

@rekna1 This issue does not contain enough information for us to be able to reproduce what is going on. Could you provide a full code listing or project that exhibits the issue?

Sorry for late reaction. It s not possible to for me to show the full project. Is there an example project showing the use of connection pooling in asp.net core webapi project using ef core with repository pattern? Will try to make small scale project but will take some time.

@rekna1 I was actually able to reproduce this in a console application, so no need to dig further.

Note for triage: Works fine when AddDbContextPool is replaced by AddDbContext

```C#
public interface IMyDbContext
{
DbSet Foos { get; set; }
}

public class MyDbContext : DbContext, IMyDbContext
{
public MyDbContext(DbContextOptions options)
: base(options)
{
}

public DbSet<Foo> Foos { get; set; }

}

public class Foo
{
public int Id { get; set; }
}

public class Program
{
public static void Main()
{
var services = new ServiceCollection()
.AddScoped(provider => provider.GetService())
.AddDbContextPool(options =>
{
options.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;ConnectRetryCount=0");
})
.BuildServiceProvider();

    using (var scope = services.CreateScope())
    {
        var context = (DbContext)scope.ServiceProvider.GetService<IMyDbContext>();
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();
    }

    Parallel.For(0, 100, s =>
    {
        using (var scope = services.CreateScope())
        {
            var context = scope.ServiceProvider.GetService<IMyDbContext>();

            Console.WriteLine("Using " + context.GetHashCode());
            context.Foos.Add(new Foo());
            ((DbContext) context).SaveChanges();
            var _ = context.Foos.ToList();
        }
    });
}

}
```

Ok great. Will revert to EF without context pooling until fixed.

@ajcvickers Is the context interface significant here?

Related to aspnet/DependencyInjection#585

It looks like this only occurs when the context is accessed indirectly via the registered interface.

Verified the issue is caused by doubly disposing the context.

@anpete Yes, but I think you knew that by now. With regard to getting disposed twice, the IDisposable interface explicitly allows that, so I think it's something we have to handle. From MSDN:

If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times. Instance methods other than Dispose can throw an ObjectDisposedException when resources are already disposed.

Adding that another workaround is to only register the context once in DI. I.e. remove the registration for IMyDbContext

Was this page helpful?
0 / 5 - 0 ratings