-1

having EF core 2.2 and .net core 2.2 I am struggling with ObjectDisposedException issues like: here and here

few facts:

  • all my services are registered as Transient, same with DbContext using AddDbContext()
  • using DI to inject DbContext instance
  • all functions mentioned in stack trace are async/await

I feel that I am missing something obvious here but I have spent already 2-3 days on this without any luck

stack trace:

Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware [8] - An unhandled exception has occurred while executing the request. 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: 'BaseContext'.
   at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Internal.IDbContextDependencies.get_QueryProvider()
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ParameterExtractingExpressionVisitor..ctor(IEvaluatableExpressionFilter evaluatableExpressionFilter, IParameterValues parameterValues, IDiagnosticsLogger`1 logger, DbContext context, Boolean parameterize, Boolean generateContextAccessors)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryModelGenerator.ExtractParameters(IDiagnosticsLogger`1 logger, Expression query, IParameterValues parameterValues, Boolean parameterize, Boolean generateContextAccessors)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.System.Collections.Generic.IAsyncEnumerable<TResult>.GetEnumerator()
   at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable`1 source, TAccumulate seed, Func`3 accumulator, Func`2 resultSelector, CancellationToken cancellationToken) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\Aggregate.cs:line 118
   at Sample.Infrastructure.Tasks.TagEventTypeTasks.GetAllAsync() in ~root\Sample.API\Sample.Infrastructure\Tasks\TagEventTypeTasks.cs:line 24
   at Sample.API.Helpers.GraphHelpers.GetAllTagEventTypesWithCacheAsync() in ~root\Sample.API\Sample.API\Helpers\GraphHelpers.cs:line 45
   at Sample.API.Controllers.GraphController.GetTagEventTypeTimesAsync() in ~root\Sample.API\Sample.API\Controllers\GraphController.cs:line 59
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)

for full transparency, here are the methods mentioned in stack trace:

GraphController.cs:

[HttpGet("tagEventTypeTimes")]
public async Task<ActionResult<List<TagEventTypeResultDto>>> GetTagEventTypeTimesAsync()
{
    return await _graphHelpers.GetAllTagEventTypesWithCacheAsync();
}

GraphHelpers.cs:

public async Task<List<TagEventType>> GetAllTagEventTypesWithCacheAsync()
{
    string tagEventTypesKey = "tagEventTypes";
    List<TagEventType> tagEventTypes;
    if (!_cache.TryGetValue<List<TagEventType>>(tagEventTypesKey, out tagEventTypes))
    {
        tagEventTypes = await _tagEventTypeTasks.GetAllAsync();
        _cache.Set<List<TagEventType>>(tagEventTypesKey, tagEventTypes, _memCacheOptions);
    }

    return tagEventTypes;
}

TagEventTypeTasks.cs:

public class TagEventTypeTasks : ITagEventTypeTasks
{
    private readonly BaseContext _context;

    public TagEventTypeTasks(BaseContext context)
    {
        _context = context;
    }

    public async Task<List<TagEventType>> GetAllAsync()
    {
        return await _context.TagEventType.OrderByDescending(x => x.Id).AsNoTracking().ToListAsync();
    }
}

BaseContext.cs:

public class BaseContext : DbContext
{
    private readonly ILoggerFactory _logger;

    public BaseContext(DbContextOptions<BaseContext> options, ILoggerFactory logger) : base(options)
    {
        _logger = logger;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseLoggerFactory(_logger);
    }

    public DbSet<Patient> Patient { get; set; }
    public DbSet<Room> Room { get; set; }
    public DbSet<Tag> Tag { get; set; }
    public DbSet<TagEvent> TagEvent { get; set; }
    public DbSet<TagEventType> TagEventType { get; set; }
    public DbSet<TagLocation> TagLocation { get; set; }
    public DbSet<TagRegistration> TagRegistration { get; set; }
    public DbSet<User> User { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfiguration(new PatientConfiguration());
        builder.ApplyConfiguration(new TagConfiguration());
        builder.ApplyConfiguration(new TagRegistrationConfiguration());
        builder.ApplyConfiguration(new TagEventConfiguration());
        builder.ApplyConfiguration(new TagEventTypeConfiguration());
        builder.ApplyConfiguration(new TagLocationConfiguration());
        builder.ApplyConfiguration(new RoomConfiguration());
    }
}

UPDATE: added startup related code

services.AddDbToServices(Configuration.GetConnectionString("DefaultConnection"));

public static void AddDbToServices(this IServiceCollection services, string connectionString)
{
    services.AddDbContext<BaseContext>(options => options.UseFirebird(connectionString), ServiceLifetime.Transient);
}

UPDATE2

added whole TagEventTypeTasks class

UPDATE3

added services register I am using this lib however I tried to register all services manually using AddTransient() -> also didn't help

var assembliesToScan = new[]
{
    Assembly.GetExecutingAssembly(),
    Assembly.GetAssembly(typeof(Patient)),
    Assembly.GetAssembly(typeof(PatientTasks))
};

services
    .RegisterAssemblyPublicNonGenericClasses(assembliesToScan)
    .AsPublicImplementedInterfaces(ServiceLifetime.Transient);
pandemic
  • 1,135
  • 1
  • 22
  • 39
  • As mentioned in one of the answers you referenced, are any other methods being called defined as `async void`? Maybe in the `_cache.TryGetValue`? – devNull Nov 05 '19 at 23:19
  • _cache is type of `IMemoryCache` and `TryGetValue()` returns bool. I searched in whole solution for `async void` with 0 results – pandemic Nov 05 '19 at 23:22
  • 1
    I'd suggest altering `BaseContext` to override https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.dbcontext.dispose?view=entity-framework-6.2.0#System_Data_Entity_DbContext_Dispose_System_Boolean_ . Put a breakpoint in it. Then see when the breakpoint gets hit (and look up the stack trace). This may help narrow down why it is being disposed earlier than expected. – mjwills Nov 05 '19 at 23:31
  • `Rethrow` in the stack trace makes me think possibility another exception is being thrown - but just a guess. – mjwills Nov 05 '19 at 23:32
  • Please show how `TagEventTypeTasks` is registered. In short, we need a [mcve]. – mjwills Nov 05 '19 at 23:35
  • updated with update3 – pandemic Nov 05 '19 at 23:40
  • Any luck with the override idea? – mjwills Nov 05 '19 at 23:41
  • this happens only on one env (only one core). second env and my local env (both multiple cores, dunno if it makes a difference) is ok. will take quite time to push and build. – pandemic Nov 05 '19 at 23:43
  • Does it work with no DI? – tymtam Nov 05 '19 at 23:50
  • yes it does @tymtam any idea what is wrong with my DI setup? – pandemic Nov 06 '19 at 00:28
  • 1
    Ok, I think `_graphHelpers` disposes the context, and next time the action is executed the controller is using the not-transient `_graphHelpers` which hold on to the old context. – tymtam Nov 06 '19 at 02:08

1 Answers1

-1

This looks like a dependency injection issue.

I think _graphHelpers disposes the context, and next time the action is executed the controller is using the not-transient _graphHelpers which holds on to the old, disposed context.

It could even be the controller calling Dispose() on _graphHelpers.


Some DI examples

Here is a setup from Get Started with ASP.NET Core and Entity Framework 6

(...)
services.AddScoped<SchoolContext>(_ => new SchoolContext(Configuration.GetConnectionString("DefaultConnection")

Tutorial: Get started with EF Core in an ASP.NET MVC web app uses AddDbContext Please note that this example is for .NET Core 2.2, not 3.0.

(...)
services.AddDbContext<SchoolContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Here is the configuration from Configuring a DbContext

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<BloggingContext>(options => options.UseSqlite("Data Source=blog.db"));
}

No DI solution

You could use new context to fetch the data, not _context.

public async Task<List<TagEventType>> GetAllAsync()
{
    // If you want DI, then do this only Temporarily, just for a diagnosis.
    using (var db = new InverterContext("connString")) 
    {
       return await db.TagEventType.OrderByDescending(x => x.Id).AsNoTracking().ToListAsync();
    }
}
tymtam
  • 31,798
  • 8
  • 86
  • 126
  • should I instantiate new context everytime I want to use it? does it mean that I shouldnt use DI? // updated my question with startup code – pandemic Nov 05 '19 at 23:29
  • You don't need to use `DI`. It's OK to instantiate the context per request. – tymtam Nov 05 '19 at 23:40