Open Closed

IRepository<T> prematurely disposes context for FullAuditedEntity during BackgroundJob #7219


0
mightyit created

The generic repository for an entity with the [Audited] decoration will cause an exception due to DbContext being disposed prematurely. I am using Hangfire.

 [Audited]
    public class MyAuditedEntity: FullAuditedEntity<Guid>
    {
        public virtual string Name { get; set; }
        public virtual string Description { get; set; }
    }
public class TroubleshootingBackgroundJob : BackgroundJob<TroubleshootingBackgroundJobArgs>, TransientDependency
{
    private readonly IRepository<MyAuditedEntity, Guid> _repository;

    public TroubleshootingBackgroundJob(IRepository<MyAuditedEntity, Guid> repository)
    {
        _repository = repository;
    }

    [UnitOfWork]
    public override void Execute(TroubleshootingBackgroundJobArgs args)
    {
        repository.GetAll().ToList(); // <== Generates the exception
    }
}

Exception details:

System.ObjectDisposedException HResult=0x80131622 Message=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. ObjectDisposed_ObjectName_Name Source=Microsoft.EntityFrameworkCore StackTrace: at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.get_Model() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_EntityType() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_EntityQueryable() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.System.Linq.IQueryable.get_Provider() at System.Linq.Queryable.Where[TSource](IQueryable1 source, Expression`1 predicate)

Am I missing something or is this a bug?

UPDATE It seems the problem is with all types of FullAuditedEntity, not the [Audited] decoration...


19 Answer(s)
  • 0
    ryancyq created

    can you share full error stacktrace?

    could not find TroubleshootingBackgroundJob in the stacktrace you shared above

  • 0
    mightyit created

    @ryancyq It is the only line omitted from the Stacktrace as I actually copied it from production code. I indicated the originating line in the code snippet with the comment // <== Generates the exception

  • 0
    ryancyq created

    @mightyit having the comple error stacktrace helps us to understand the problem better. e.g the job execution lifecycle.

    without it, it is hard to debug the problem.

  • 0
    mightyit created

    Here it is, but, as I mentioned, there really is no other useful information:

    System.ObjectDisposedException HResult=0x80131622 Message=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. ObjectDisposed_ObjectName_Name Source=Microsoft.EntityFrameworkCore StackTrace: at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.get_Model() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_EntityType() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_EntityQueryable() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.System.Linq.IQueryable.get_Provider() at System.Linq.Queryable.Where[TSource](IQueryable1 source, Expression`1 predicate) at MyNameSpace.TroubleshootingBackgroundJob.Execute(TroubleshootingBackgroundJobArgs args) in REDACTED\TroubleshootingBackgroundJob.cs:line 17

  • 0
    mightyit created

    @ryancyq @ismcagdas were you able to recreate the issue?

  • 0
    ismcagdas created

    Have you marked Execute method of your BackgroundJob with UnitOfWork attribute ?

    [UnitOfWork]
    public override void Execute(TestJobArgs args)
    {
    	// ...
    }
    
  • 0
    mightyit created

    Hi @ismcagdas

    Yes, I have. I am still getting the same issue though.

  • 0
    alper created

    to filter down the issue, make a simple entity like below, this will show us if there's a problem with writing audit log after DbContext disposes.

     //[Audited]  removed
        public class MyAuditedEntity:  Entity<Guid> // FullAuditedEntity<Guid>
        {
            public virtual string Name { get; set; }
            public virtual string Description { get; set; }
        }
    
  • 0
    mightyit created

    @alper the issue persists

  • 0
    alper created

    Try this workaround

    [UnitOfWork]
    protected override void Execute(TroubleshootingBackgroundJobArgs args)
    {
         DoJob();
    }
        
    [UnitOfWork]
    protected virtual void DoJob()
    {
         repository.GetAll().ToList();
    }
    
  • 0
    alper created

    if the above solution doesn't work, try to create a new transaction like below

     [UnitOfWork]
     public override void Execute(TroubleshootingBackgroundJobArgs args) 
     { 
           using(var uow = UnitOfWorkManager.Begin()) 
           {
                 repository.GetAll().ToList();
                 uow.Complete();
           }   
     }
    
  • 0
    mightyit created

    Hi @alper

    Thanks for the assistance. I have tried both ways, unsuccesfully unfortunately.

    However, when I did this...

     [UnitOfWork]
     public override void Execute(TroubleshootingBackgroundJobArgs args) 
     { 
           using(var uow = UnitOfWorkManager.Begin()) // <== Exception generated here...
           {
                 repository.GetAll().ToList();
                 uow.Complete();
           }   
     }
    

    .... I got the following exception:

    Abp.AbpException HResult=0x80131500 Message=Must set UnitOfWorkManager before use it. Source=Abp StackTrace: at Abp.BackgroundJobs.BackgroundJob1.get_UnitOfWorkManager() at Sanctions.SearchServices.Infrastructure.UnscConsolidatedSanctionList.Infrastructure.Persons.PersonMonitoringBatch.PersonMonitoringBatchSearchJob.ExecuteJob(PersonMonitoringBatchSearhJobArgs args) in D:\Projects\ZenDetect\Modules\Sanctions\Sanctions.SearchServices.Infrastructure.UnscConsolidatedSanctionList\Infrastructure\Persons\PersonMonitoringBatch\PersonMonitoringBatchSearchJob.cs:line 54 at Sanctions.SearchServices.Infrastructure.UnscConsolidatedSanctionList.Core.BatchSearchJobBase1.Execute(TBatchSearhJobArgs args) in D:\Projects\ZenDetect\Modules\Sanctions\Sanctions.SearchServices.Infrastructure.UnscConsolidatedSanctionList\Core\BatchSearchJobBase.cs:line 20

    Seems like the Unit of Work object is not resolving, for some reason?

  • 1
    alper created

    Weird! Then inject UnitOfWorkManager traditionally. Let's see if UnitOfWorkManager is being resolved successfully.

    public class TroubleshootingBackgroundJob : BackgroundJob<TroubleshootingBackgroundJobArgs>, TransientDependency
    {
        private readonly IRepository<MyAuditedEntity, Guid> _repository;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
    
        public TroubleshootingBackgroundJob(IRepository<MyAuditedEntity, Guid> repository, IUnitOfWorkManager unitOfWorkManager)
        {
            _repository = repository;
            _unitOfWorkManager = unitOfWorkManager;
        }
    
        [UnitOfWork]
        public override void Execute(TroubleshootingBackgroundJobArgs args)
        {
           using(var uow = _unitOfWorkManager.Begin()) // <== Exception generated here...
           {
                 repository.GetAll().ToList();
                 uow.Complete();
           }   
        }
    }
    

    Also you can try disabling UnitOfWork... This might give us some clue!

    [UnitOfWork(IsDisabled = true)]
       public override void Execute(TroubleshootingBackgroundJobArgs args)
        { 
                 repository.GetAll().ToList();           
        }
    
  • 0
    mightyit created

    OK, interesting....

    • Explicitly injecting IUnitOfWork, as you suggested, seems to work.
    • Running with the attribute [UnitOfWork(IsDisabled = true)] throws the original exception

    Why would the IUnitOfWork not resolve for the BackgroundJob<TArgs> base class? How do I go about fixing it?

  • 0
    alper created

    first of all I see that you use TransientDependency.

    public class TroubleshootingBackgroundJob : BackgroundJob<TroubleshootingBackgroundJobArgs>, TransientDependency
    

    Is it some custom class that you've made? because the original one is ITransientDependency.

    And you can use GetAllList as well which also gets a predicate.

    [UnitOfWork]
        public override void Execute(TroubleshootingBackgroundJobArgs args)
        {
          
                 repository.GetAllList()
              
        }
    
  • 0
    mightyit created

    @alper

    Yes, that's a typo. It should be (and is) ITransientDependency.

  • 0
    ismcagdas created

    Hi @mightyit

    Is this problem resolved ?

    Thanks,

  • 0
    mightyit created

    Hi @ismcagdas

    No. I had to use workarounds.

  • 0
    tinytownsoftware created

    [UnitOfWork] Fixed it for me.