Open Closed

Proper way to use AddDbContextFactory instead of AddAbpDbContext #8473


User avatar
0
alexander.nikonov created
  • ABP Framework version: v8.1.3
  • UI Type: Angular
  • Database System: EF Core (Oracle)
  • Auth Server Separated OpenID Server

We utilize a Hangfire server. So far its module has used a standard EF setup suggested by ABP framework:

public class BatchEntityFrameworkCoreModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<BatchDbContext>(options =>
        {
            ...
        });

        context.Services.AddAbpDbContext<InternalCoreDbContext>(options =>
        {
            ...
        });

        Configure<AbpDbContextOptions>(options =>
        {
            options.PreConfigure(abpDbContextConfigurationContext =>
            {
                abpDbContextConfigurationContext.DbContextOptions.UseLoggerFactory(LoggerFactory.Create(loggingBuilder => loggingBuilder.AddConsole()));                    
                abpDbContextConfigurationContext.DbContextOptions.EnableSensitiveDataLogging();
            });
           
            options.UseOracle(options => options.MaxBatchSize(100));
            options.UseMultiDb(context.Services.GetConfiguration());
        });

        context.Services.AddTransient<IAuditPropertySetter, Abp.DataExtensions.Entities.LogAuditPropertySetter>();        
        AbxEfSettingProvider.Initialize(context.Services.GetConfiguration());
    }
}

However when we run two time-consuming jobs on this server, the second job raises the exception, if the first job is still in progress:

An attempt was made to use the context instance while it is being configured. A DbContext instance cannot be used inside 'OnConfiguring' since it is still being configured at this point. This can happen if a second operation is started on this context instance before a previous operation completed. Any instance members are not guaranteed to be thread safe.

I have found the solution where a user switches from DbContext to DbContextFactory using the setup:

builder.Services.AddDbContextFactory<PowerBlazorDbContext>(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("PowerBlazorDb")));    

And then uses the following DI:

public class PowerService : IPowerService
{
    private readonly IDbContextFactory<PowerBlazorDbContext> _contextFactory;
    public PowerService(IDbContextFactory<PowerBlazorDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    //Accessing the DbContext
    public async Task<...> SomethingAsync(...)
    {
        await using var _context = await _contextFactory.CreateDbContextAsync();
        ...
    }
}

How should we properly switch from AddAbpDbContext to AddDbContextFactory? Or you may have other solutions?


9 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can use Hangfire without AddAbpDbContext, Just using an EF COreDbContext with the correct connection string.

  • User Avatar
    0
    alexander.nikonov created

    Hi. Sorry, I am not sure I understand. I use AddAbpDbContext extension as in the rest of the projects, because Hangfire server jobs consume our custom repositories DI which are based on the relevant AbpDbContext:

        public class SomeBatchRepository : EfCoreRepository<BatchDbContext>, IRepository<...> ...
        public class SomeOtherRepository : EfCoreRepository<InternalCoreDbContext>, IRepository<...> ...
        
        public class InternalCoreDbContext : AbpDbContext<InternalCoreDbContext> ...
        public class BatchDbContext: AbpDbContext<BatchDbContext> ...
        
    

    And what do you mean specifically under "EF COreDbContext"?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can start a new unit of work in your job class to prevent this exception.

    Please share the code of your job class.

    Thanks.

    An attempt was made to use the context instance while it is being configured. A DbContext instance cannot be used inside 'OnConfiguring' since it is still being configured at this point. This can happen if a second operation is started on this context instance before a previous operation completed. Any instance members are not guaranteed to be thread safe.

    https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work#begin-a-new-unit-of-work

  • User Avatar
    0
    alexander.nikonov created

    Thank you. Whereas I cannot share actual code, I can show the way DbContext is used. So any Hangfire job can be scheduled at any time (they can interpose) and do any kind of CRUD operation:

        public class ContextAgnosticBatchAppService : IContextAgnosticBatchAppService
        {
            private readonly BatchDbContext _dbContext;
            private readonly IBatchRepository _batchRepository;
            ...
        
            public ContextAgnosticBatchAppService
            (
                BatchDbContext dbContext,
                IBatchRepository batchRepository,
                ...
            )
            {
                _dbContext = dbContext;
                _batchRepository = batchRepository;
                ...
            }
            
            [NonAction]
            public virtual async Task<...> AddForServerAsync(...)
            {
                var data = await _batchRepository.InsertAsync(...);
                ...        
                return ...;
            }
        
            [NonAction]
            public virtual async Task<...> UpdateForServerAsync(...)
            {
                await _dbContext.Batches.Where(...)
                    .ExecuteUpdateAsync
                    (
                        x => x.SetProperty(...)
                    );
        
                return ...;
            }
        	...
        }
        
    

    Repositories use DbContext in the following way:

    var dbContext = await GetDbContextAsync();
    

    All Hangfire jobs work with the same database, so even though each job is supposed to work with its own tenant - any possible database data collisions need to be avoided, but at the same time it is desirable that any job was aware about the changes already done by other job (e.g. added entities).

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok

    Inject the IDbContextProvider<BatchDbContext> then call GetDbContextAsync method to get dbcontext.

    and use dbcontext in a new uow. https://abp.io/docs/latest/framework/architecture/domain-driven-design/unit-of-work#begin-a-new-unit-of-work

  • User Avatar
    0
    alexander.nikonov created

    Thank you, I will try and let you know.

    Some more questions.

    A) with my old approach (i.e. having BatchDbContext _dbContext DI in IApplicationService) I used the following approach to speed-up DB operations involving creating many records:

        _dbContext.ChangeTracker.AutoDetectChangesEnabled = false;
        (addedCount, addedLines) = await TryCreateChunkAsync(...); //inserting some data into DB by chunks - each chunk is created in a separate UnitOfWork
        _dbContext.ChangeTracker.AutoDetectChangesEnabled = true;
        
    

    But now when I switch to IDbContextProvider<BatchDbContext> _dbContextProvider DI - what is the corresponding change here? Should I still accomplish this using var dbContext = await _dbContextProvider.GetDbContextAsync(); dbContext.ChangeTracker.AutoDetectChangesEnabled = ... or those ChangeTracker operations do not make sense anymore?

    B) if I do not interact with DbContext directly, do I still need to make additional changes for my scenario in the following method?

        [NonAction]
        public virtual async Task&lt;...&gt; InsertAsync(...)
        {
            using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) //Adding Unit Of Work
            {
                    var entity = _objectMapper.Map&lt;...&gt;(input);
                    //Any additional changes related to using DbContext??
                    var newEntity = await _entityRepository.InsertAsync(entity);
                    await uow.CompleteAsync();
                    return _objectMapper.Map&lt;...&gt;(newEntity);
            }
        }
        
    

    C) Any modifications required for methods which do not change the data in DB (i.e. read methods)? What is important - I still want to be sure that such a method "sees" data which have been added by other Hangfire job by this moment.

    And saying honestly, I do not know why the exception like mine ever happens. Indeed, the documentation which link you have provided, mentions that "Application service methods" are considered as a unit of work. Which means, that none of DB changes done by one app service should conflict with those of other app service, right? Maybe the reason is that all my app services in Hangfire server implement IApplicationService, but do not inherit from ApplicationService of ABP - so they are not considered as a Unit Of Work? This is done intentionally and I want to leave it like this. I've tried to implement IUnitOfWorkEnabled in all my services instead of doing the other changes you suggested, but then I got the exceptions (which need to be investigated, but probably it is not the way to go). I am confused with all possible approaches which are suggested. Could you please explain, if they are mutually exclusive or complementary?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    A & B: If it was previously applicable to your case, you can continue to make such changes. This is not abp framework code.

    C: ABP has the read-only repository: https://github.com/abpframework/abp/pull/17491

  • User Avatar
    0
    alexander.nikonov created

    Hi. The easiest way appeared to be just to decorate a whole custom AppService class with [UnitOfWork] attribute, because this custom service was not inherited from ApplicationService of ABP. So I did not need to manually create uow everywhere in the methods. Thank you.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok, You can try this solution. : )

Made with ❤️ on ABP v9.1.0-preview. Updated on December 13, 2024, 06:09