Skip to content
This repository was archived by the owner on Nov 2, 2018. It is now read-only.

Cannot access a disposed object in ASP.NET Core when injecting DbContext #440

Closed
mdmoura opened this issue Aug 1, 2016 · 15 comments
Closed
Assignees
Labels
investigate Investigation item

Comments

@mdmoura
Copy link

mdmoura commented Aug 1, 2016

On an ASP.NET Core project I have the following on Startup:

      services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));

      services.AddScoped<IValidationService, ValidationService>();

      services.AddScoped<IValidator<Model>, ModelValidator>();

The ValidationService is as follows:

    public interface IValidationService {
      Task<List<Error>> ValidateAsync<T>(T model);
    }

  public class ValidationService : IValidationService {

    private readonly IServiceProvider _provider;

    public ValidationService(IServiceProvider provider) {
      _provider = provider;
    }

    public async Task<List<Error>> ValidateAsync<T>(T model) {

      IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();

      return await validator.ValidateAsync(model);

    }
  }

And the ModelValidator is as follows:

    public class ModelValidator : AbstractValidator<Model> {
      public ModelValidator(Context context) {
        // Some code using context
      }
    }

When I inject a IValidationService in a controller and use it as:

    List<Error> errors = await _validator.ValidateAsync(order);    

I get the error:

System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. 
This may occur is you are calling Dispose() on the context, or wrapping the context in a using statement. 
If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'Context'.    

Any idea why I am having this error when using Context inside ModelValidator?

@Eilon Eilon added the investigate Investigation item label Aug 1, 2016
@Eilon
Copy link
Contributor

Eilon commented Aug 1, 2016

@javiercn can you take a look to see what's going on here?

@javiercn
Copy link
Member

javiercn commented Aug 2, 2016

I can't reproduce this. @mdmoura can you provide a small repro project? Here is what I tried.

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        var connectionString = @"Data Source = (localdb)\v11.0; Initial Catalog = PeopleContext; Integrated Security = True; Connect Timeout = 30; Encrypt = False; TrustServerCertificate = True; ApplicationIntent = ReadWrite; MultiSubnetFailover = False";
        services.AddDbContext<Context>(c => c.UseSqlServer(connectionString));

        services.AddScoped<IValidationService, ValidationService>();
        services.AddScoped<IValidator<Person>, ModelValidator>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, Context ctx)
    {
        ctx.Database.EnsureCreated();
        app.UseDeveloperExceptionPage();
        app.UseStaticFiles();
        loggerFactory.AddConsole();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseKestrel()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

public class HomeController : Controller
{
    private readonly IValidationService _validationService;

    public HomeController(IValidationService validationService)
    {
        _validationService = validationService;
    }

    public IActionResult Index()
    {
        return View();
    }


    [HttpPost("/Person")]
    public async Task<IActionResult> Post([FromBody] Person person)
    {
        var errors = await _validationService.ValidateAsync(person);
        if (errors.Count > 0)
        {
            return BadRequest(errors);
        }
        else
        {
            return Ok(person);
        }
    }
}

public class Context : DbContext
{
    public Context(DbContextOptions<Context> options) : base(options)
    {
    }

    public DbSet<Person> People { get; set; }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public interface IValidationService
{
    Task<IList<Error>> ValidateAsync<T>(T model);
}

public class ValidationService : IValidationService
{
    private readonly IServiceProvider _provider;

    public ValidationService(IServiceProvider provider)
    {
        _provider = provider;
    }

    public async Task<IList<Error>> ValidateAsync<T>(T model)
    {
        var validator = _provider.GetRequiredService<IValidator<T>>();
        return await validator.ValidateAsync(model);
    }
}

public class ModelValidator : IValidator<Person>
{
    private readonly Context _ctx;

    public ModelValidator(Context context)
    {
        _ctx = context;
    }

    public async Task<IList<Error>> ValidateAsync(Person model)
    {
        var person = await _ctx.People.FirstOrDefaultAsync(p => p.Id == model.Id);
        return person != null ? new List<Error> { new Error("Person already exists") } : Enumerable.Empty<Error>().ToList();
    }
}

public interface IValidator<T>
{
    Task<IList<Error>> ValidateAsync(T model);
}

public class Error
{
    public Error(string description)
    {
        Description = description;
    }

    public string Description { get; set; }
}

@mdmoura
Copy link
Author

mdmoura commented Aug 2, 2016

@javiercn I found the problem. On Startup I was seeding data to the database as follows:

if (hostingEnvironment.IsDevelopment())
  applicationBuilder.SeedData();

Where the extension method SeedData is:

public static class DataSeedExtensions {
  private static IServiceProvider _provider;
  public static void SeedData(this IApplicationBuilder builder) {
    _provider = builder.ApplicationServices;
     using (Context context = (Context)_provider.GetService<Context>()) {
        await context.Database.MigrateAsync();
        // Insert data in database
      }
  }
}

Then I changed it to (added the IServiceScope):

public static class DataSeedExtensions {
  private static IServiceProvider _provider;
  public static void SeedData(this IApplicationBuilder builder) {
    _provider = builder.ApplicationServices;
    using (IServiceScope scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
      Context context = _provider.GetService<Context>();
      // Insert data in database
    }
  }
}

Now I do not get the error anymore when injecting Context into a Validator ...

Should the 'using (Context ...)' affect the application when running afterwards?

This is not something obvious to find ...

@khellang
Copy link
Contributor

khellang commented Aug 2, 2016

Hmm. Looks like the issue is that you're resolving a "scoped" instance from the top-level provider, which effectively makes it a singleton.

Since you've wrapped it in a using block, it's disposed, but left in the provider. When you resolve this service the next time, you get the same, disposed instance, hence the ObjectDisposedException.

The reason it doesn't reproduce in @javiercn's sample, is because controllers' dependencies (IValidationService) is resolved from the request-scoped provider.

I think this has been solved with #430, where the provider will throw if a scoped services is resolved from the application provider.

The solution is, as you've already figured out, to create a scope and resolve the context from that, like this:

public static class DataSeedExtensions
{
    public static void SeedData(this IApplicationBuilder builder)
    {
        var provider = builder.ApplicationServices;
        var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();

        using (var scope = scopeFactory.CreateScope())
        using (var context = scope.Provider.GetRequiredService<Context>())
        {
            // Use context to insert data in database...
        }
    }
}

@Eilon Eilon closed this as completed Aug 2, 2016
@newdygo
Copy link

newdygo commented Oct 4, 2017

Aparently this error is caused by the readonly property where you store the Context or the Service you injected.

@tiagorosendo
Copy link

tiagorosendo commented Oct 24, 2017

I have this problem when I make multiple requests at the same time, does anyone have an idea of what it might be?

Startup:
            services.AddDbContextPool<DbContext >(options =>
            {
                options.UseSqlServer(Configuration.GetValue<string>("ConString"), opts => splitOptions.EnableRetryOnFailure())
                       .EnableSensitiveDataLogging();
            });

Reader:

     public DbContext DbContext { get; }

        protected DbSet<Item> Table => DbContext .Item;

        public ItemReader(DbContext dbContext)
        {
            DbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
        }

        public async Task<Maybe<Item>> Read(Guid itemId)
        {
            var items= await Table
                                    .Include(t => t.TableName)
                                    .ThenInclude(m => m.TableName)
                                    .Include(t => t.TableName)
                                    .ThenInclude(m => m.TableName)
                                    .Include(t => t.TableName)
                                    .ThenInclude(sp => sp.TableName)
                                    .Include(t => t.TableName)
                                    .ThenInclude(sp => sp.TableName)
                                    .ThenInclude(ss => ss.TableName)
                                    .Include(t => t.TableName)
                                    .ThenInclude(sp => sp.TableName)
                                    .ThenInclude(ss => ss.TableName)
                                    .AsNoTracking()
                                    .FirstOrDefaultAsync(t => t.Id == itemId);

            if (item.IsNull())
                return Maybe<Item>.None;

            return item;
        }

@khellang
Copy link
Contributor

@tiagorosendo And where/how are you getting the ItemReader?

@tiagorosendo
Copy link

tiagorosendo commented Oct 24, 2017

@khellang


    public class ItemReaderWithExceptionHandler: IItemReader 
    {
        public IItemReader ItemReader { get; }
        public ILogger Logger { get; }


        public ItemReaderWithExceptionHandler(
            IItemReader itemReader,
            ILogger logger )
        {
            ItemReader  = itemReader ?? throw new ArgumentNullException(nameof(itemReader ));
            Logger = logger ?? throw new ArgumentNullException(nameof(logger));

        }

        public async Task<Maybe<Item>> Read(Guid itemId){
            try
            {
                return await ItemReader.Read(itemId)
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error..."));
                throw;
            }

}


@khellang
Copy link
Contributor

@tiagorosendo And where/how are you getting the ItemReaderWithExceptionHandler? 😝 What I'm really after is "what scope is the DbContext resolved in"?

@tiagorosendo
Copy link

tiagorosendo commented Oct 24, 2017

In controller like this :

Controller:

[Route("api/item")]
 public class ItemController : Controller
 {
     public IItemReader ItemReader{ get; }

     public ItemController (IItemReader itemReader)
     {
         IItemReader = itemReader?? throw new ArgumentNullException(nameof(itemReader));
     }

     [HttpPost]
     public async Task<IActionResult> GetItem(Guid itemId)
     {
                  var item= await ItemReader.Read(itemId);

                if(item.HasNoValue)
                    return NotFound()

                return Ok(item)
     }
 }



Decorator to log exceptions:

public class ItemReaderWithExceptionHandler: IItemReader 
 {
     public IItemReader ItemReader { get; }
     public ILogger Logger { get; }


     public ItemReaderWithExceptionHandler(
         IItemReader itemReader,
         ILogger logger )
     {
         ItemReader  = itemReader ?? throw new ArgumentNullException(nameof(itemReader ));
         Logger = logger ?? throw new ArgumentNullException(nameof(logger));

     }

     public async Task<Maybe<Item>> Read(Guid itemId){
         try
         {
             return await ItemReader.Read(itemId)
         }
         catch (Exception ex)
         {
             Logger.Error(ex, "Error..."));
             throw;
         }

}

Reader:
  public DbContext DbContext { get; }

     protected DbSet<Item> Table => DbContext .Item;

     public ItemReader(DbContext dbContext)
     {
         DbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
     }

     public async Task<Maybe<Item>> Read(Guid itemId)
     {
         var items= await Table
                                 .Include(t => t.TableName)
                                 .ThenInclude(m => m.TableName)
                                 .Include(t => t.TableName)
                                 .ThenInclude(m => m.TableName)
                                 .Include(t => t.TableName)
                                 .ThenInclude(sp => sp.TableName)
                                 .Include(t => t.TableName)
                                 .ThenInclude(sp => sp.TableName)
                                 .ThenInclude(ss => ss.TableName)
                                 .Include(t => t.TableName)
                                 .ThenInclude(sp => sp.TableName)
                                 .ThenInclude(ss => ss.TableName)
                                 .AsNoTracking()
                                 .FirstOrDefaultAsync(t => t.Id == itemId);

         if (item.IsNull())
             return Maybe<Item>.None;

         return item;
     }


@khellang
Copy link
Contributor

@tiagorosendo Can you show the registrations? By default, that dependency chain would throw a StackOverflowException, no? You have an IItemReader that depends on an IItemReader

@DuanShaolong
Copy link

The Error Message is: "Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'."

@kevinvella
Copy link

kevinvella commented Jun 26, 2018

Hi,

I have the following piece of code:

var request = (from chatRoomUsers in chatRoomUsersRepo.GetAll()
                           join chatroom in chatRoomRepo.GetAll()
                           on chatRoomUsers.cru_ChatRoomId equals chatroom.cr_Pk
                           where chatRoomUsers.cru_UserId == userId 
                           orderby chatroom.cr_CreatedAt descending
                           select new
                           {
                               ChatRoom = chatroom,
                               ChatRoomData = jsonService.Deserialize<ChatRoomData>(chatroom.cr_Data),
                               Users = (from users in userRepo.GetAll()
                                        join chatRoomUsers in chatRoomUsersRepo.GetAll()
                                        on users.usr_Pk equals chatRoomUsers.cru_UserId
                                        where chatRoomUsers.cru_ChatRoomId == chatroom.cr_Pk
                                        select users)
                           }).ToList().Select(x => new ChatRoom
                           {
                               Id = x.ChatRoom.cr_Pk,
                               Type = x.ChatRoom.cr_Type == 1 ? "P2P" : "Group Chat",
                               Data = new ChatRoomData()
                               {
                                   Name = x.ChatRoomData.Name != null ? x.ChatRoomData.Name : string.Join(", ", x.Users.Where(z => z.usr_Pk != userId).Select(y => $"{y.usr_Firstname} {y.usr_LastName}").FirstOrDefault()),
                                   ProfilePicURL = x.ChatRoom.cr_Type == 1 ? x.Users.Where(z => z.usr_Pk != userId).FirstOrDefault().usr_ImageProfileUrl : null,
                               },
                               Users = x.Users.Select(y => $"{y.usr_Firstname} {y.usr_LastName}").ToList(),
                               Guid = x.ChatRoom.cr_Guid,
                           }).ToList();

As you see the Users are being fetched by a sub query. The first time the server is called the query returns the list successfully. Subsequent queries will result in the following error:

{System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ApplicationContext'.
   at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
   at Microsoft.EntityFrameworkCore.DbContext.get_Database()
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.OnFirstExecution()
   at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.QueryMethodProvider.<_InjectParameters>d__27`1.MoveNext()
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
   at lambda_method(Closure , QueryContext )
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass17_1`1.<CompileQueryCore>b__0(QueryContext qc)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
   at.Services.App.ChatRoomService.<>c__DisplayClass19_0.<GetByUser>b__6(<>f__AnonymousType45`3 x) in /Users/kevin/Documents/Development///.Services/App/ChatRoomService.cs:line 281
   at System.Linq.Enumerable.SelectListIterator`2.ToList()
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at .Services.App.ChatRoomService.GetByUser(Int32 userId) in /Users/kevin/Documents/Development///.Services/App/ChatRoomService.cs:line 258
   at .Web.Controllers.ChatRoomController.GetChatrooms(String UserEmail) in /Users/kevin/Documents/Development///.Web/Controllers/ChatRoomController.cs:line 289}

If I add .ToList() to the subquery everything works just fine.

Services that are registered with the app:

services.AddDbContext<ApplicationContext>(options => 
            {
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnectionString"), opts => 
                {
                    opts.EnableRetryOnFailure();
                });
                options.EnableSensitiveDataLogging();
            });

            services.AddScoped(typeof(IRepository<>), typeof(GenericRepository<>));
            services.AddScoped<IUnitOfWork, UnitOfWork>();

            services.AddTransient<IThumbnailService, SkiaSharpThumbnailService>();

            services.AddTransient<SHA512CryptographyService>();
            services.AddTransient<MD5CryptographyService>();
            services.AddTransient<ICryptographyService, BCryptCryptographyService>();

            services.AddTransient<IXmlService, XmlService>();
            services.AddTransient<IJsonService, JsonService>();
            services.AddTransient<IHttpService, HttpClientService>();

            //Get Email Configuration
            IConfigurationSection emailSection = Configuration.GetSection("EmailProviders");
            EmailSettings val = emailSection.Get<EmailSettings>();
            services.AddSingleton<IConfiguration>(Configuration);
            services.AddTransient<IFacebookClient, FacebookClient>();
            services.AddTransient<IFacebookService, FacebookService>();

            services.AddTransient<IUserService, UserService>();
            services.AddTransient<IUserBlockingService, UserBlockingService>();

            
            services.AddTransient<ISportAPIService, SportsRadarService>();
            services.AddTransient<IImportService, SportsRadarImportService>();

            //Fixture service
            services.AddTransient<IFixtureService, FixtureService>();
            services.AddTransient<IFlaggedContentService, FlaggedContentService>();
            services.AddTransient<ILeagueService, LeagueService>();
            services.AddTransient<ICompetitorService, CompetitorService>();
            services.AddTransient<IBetService, BetService>();
            services.AddTransient<ILeaderboardService, LeaderBoardService>();
            services.AddTransient<IGroupService, GroupService>();
            services.AddTransient<ILogService, LogService>();

            services.AddTransient<ILeaderBoardBetService, LeaderBoardBetService>();
            services.AddTransient<IOrderService, OrderService>();
            services.AddTransient<ISettingService, SettingService>();
            services.AddTransient<ISportService, SportService>();


            //ChatRoom Bets Service
            services.AddTransient<IChatRoomBetService, ChatRoomBetService>();
            services.AddTransient<IChatRoomService, ChatRoomService>();
            services.AddTransient<IChatRoomUserService, ChatRoomUserService>();
            services.AddTransient<IChatMessageService, ChatMessageService>();
            services.AddTransient<IChatRoomUserBetService, ChatRoomUserBetService>();

Using AspNetCore 2.0.1 including Entity Framework

Could you please shed some light on the following?

Much appreciated

@DuanShaolong
Copy link

DuanShaolong commented Jun 26, 2018 via email

@radenkozec
Copy link

Have the same issue

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
investigate Investigation item
Projects
None yet
Development

No branches or pull requests

9 participants