Mr Perfect
Mr Perfect

Reputation: 685

How to get DBContext again after the Main Thread disposes?

Net core. I am using EF core, Unit Of Work and Repository pattern. In my code I am releasing my main thread using await Task.Delay(150); Then I am trying to get DB Context but exception says Dbcontext is disposed. So Is there any way I can get DBContext again after the main thread disposes? below few are my code snippets,

services.AddDbContext<MyContext>(options =>
           options.UseSqlServer(this.Configuration["AzureSQLConnectionString"]));
services.AddScoped<IUnitOfWork, UnitOfWork>();

Below is my method

public Class MyTestBusinessLogic : IMyTestBusinessLogic 
{
    public MyTestBusinessLogic(IUnitOfWork unitOfWork)
    {
        UnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
        MyTestRepository= UnitOfWork.GetRepository<MyTest>();
    }
    private IDbRepository<MyTest> MyTestRepository{ get; set; }
    public async Task MyMethod()
    {
        await MyTestRepository.GetFirstOrDefaultAsync(x => x.Id == Id); // works here 
        await Task.Delay(150);
        // here after my context disposes
        await MyTestRepository.GetFirstOrDefaultAsync(x => x.Id == Id); //this wont work and It will throw error inside GetFirstOrDefaultAsync method.
    }
}

GetFirstOrDefaultAsync method

public class DbRepository<T> : BaseRepository<T>, IDbRepository<T> where T : class
{
    private readonly DbContext dbContext;
    private readonly DbSet<T> dbSet;
    public DbRepository(DbContext dbContext)
    {
        this.dbContext = dbContext;
        this.dbSet = dbContext?.Set<T>();
    }

    public async Task<T> GetFirstOrDefaultAsync(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, params Expression<Func<T, object>>[] includes)
    {
        IQueryable<T> query = this.dbSet; // throws exception saying cannot use disposed object
        foreach (Expression<Func<T, object>> include in includes)
        {
            query = query.Include(include);
        }

        if (filter != null)
        {
            query = query.Where(filter);
        }

        if (orderBy != null)
        {
            query = orderBy(query);
        }

        return await query.FirstOrDefaultAsync().ConfigureAwait(false);
    }
}

Can someone help me to fix this? Any help would be appreciated. Thanks

Upvotes: 1

Views: 590

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 457057

I am calling without await

This is your problem. So-called "fire and forget" is dangerous on ASP.NET, and is only appropriate when it doesn't matter whether the code runs or not. In your case, since you need the code to run, omitting the await is inappropriate.

The correct solution is to call MyMethod with await.

If you absolutely, must return early, then the next-best solution is to introduce a reliable queue (e.g., Azure Queue / Amazon SMS) and a background processor (e.g., Azure Function / Amazon Lambda / .NET Core BackgroundService) that processes messages on that queue. Once you have these, when you have a situation where you need to return early and do the work later, then that code will place a message into the queue and then return a result. When the background processor retrieves the message from the queue, it will then do the work.

Upvotes: 2

Karan
Karan

Reputation: 12629

I presume that you are running MyMethod without awaiting it. So while main thread completes execution it will destroy dbcontext or any scoped objects injected from DI.

To fulfill your requirement you can use IServiceScopeFactory which is singleton object and added by .net core framework. Using it you can create scope using serviceScopeFactory.CreateScope() and create new instance of required dependencies using scope.ServiceProvider.GetRequiredService<MyDbContext>();.

So in such scenario you can use like below. Please refer comment for understanding. I don't know exact structure of you application so I added IServiceScopeFactory to your MyTest for example/test. But as per your code you can move it elsewhere I guess in UnitOfWork.

public Class MyTestBusinessLogic : IMyTestBusinessLogic 
{

    // Add serviceScopeFactory
    private readonly IServiceScopeFactory serviceScopeFactory;
    
    // Inject IServiceScopeFactory and assign to class object.
    public MyTestBusinessLogic(IServiceScopeFactory serviceScopeFactory)
    {
        this.serviceScopeFactory = serviceScopeFactory;
        
        // Create scope with IServiceScopeFactory.CreateScope()
        var scope = serviceScopeFactory.CreateScope();
        
        // GetRequiredService unitOfWork
        UnitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
        // UnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
        
        MyTestRepository= UnitOfWork.GetRepository<MyTest>();
    }
    
    private IDbRepository<MyTest> MyTestRepository{ get; set; }
    
    public async Task MyMethod()
    {
        await MyTestRepository.GetFirstOrDefaultAsync(x => x.Id == Id); // works here 
        await Task.Delay(150);
        // here after my context disposes
        await MyTestRepository.GetFirstOrDefaultAsync(x => x.Id == Id); //this wont work and It will throw error inside GetFirstOrDefaultAsync method.
    }
}

Upvotes: 1

Related Questions