Base solution for your next web application
Open Closed

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


User avatar
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...


20 Answer(s)
  • User Avatar
    0
    ryancyq created
    Support Team

    can you share full error stacktrace?

    could not find TroubleshootingBackgroundJob in the stacktrace you shared above

  • User Avatar
    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

  • User Avatar
    0
    ryancyq created
    Support Team

    @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.

  • User Avatar
    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

  • User Avatar
    0
    mightyit created

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

  • User Avatar
    0
    ismcagdas created
    Support Team

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

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

    Hi @ismcagdas

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

  • User Avatar
    0
    alper created
    Support Team

    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; }
        }
    
  • User Avatar
    0
    mightyit created

    @alper the issue persists

  • User Avatar
    0
    alper created
    Support Team

    Try this workaround

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

    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();
           }   
     }
    
  • User Avatar
    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?

  • User Avatar
    1
    alper created
    Support Team

    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();           
        }
    
  • User Avatar
    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?

  • User Avatar
    0
    alper created
    Support Team

    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()
              
        }
    
  • User Avatar
    0
    mightyit created

    @alper

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

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @mightyit

    Is this problem resolved ?

    Thanks,

  • User Avatar
    0
    mightyit created

    Hi @ismcagdas

    No. I had to use workarounds.

  • User Avatar
    0
    tinytownsoftware created

    [UnitOfWork] Fixed it for me.

  • User Avatar
    0
    ISTeam created

    [UnitOfWork] is the fix for me in .NET Core 3.1 template to implement a default .NET based background service.