- 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)
-
0
hi
You can use Hangfire without
AddAbpDbContext,
Just using an EF COreDbContext
with the correct connection string. -
0
Hi. Sorry, I am not sure I understand. I use
AddAbpDbContext
extension as in the rest of the projects, becauseHangfire
server jobs consume our custom repositories DI which are based on the relevantAbpDbContext
: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"?
-
0
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
-
0
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). -
0
ok
Inject the
IDbContextProvider<BatchDbContext>
then callGetDbContextAsync
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
-
0
Thank you, I will try and let you know.
Some more questions.
A) with my old approach (i.e. having
BatchDbContext _dbContext
DI inIApplicationService
) 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 usingvar 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<...> InsertAsync(...) { using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) //Adding Unit Of Work { var entity = _objectMapper.Map<...>(input); //Any additional changes related to using DbContext?? var newEntity = await _entityRepository.InsertAsync(entity); await uow.CompleteAsync(); return _objectMapper.Map<...>(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 fromApplicationService
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 implementIUnitOfWorkEnabled
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? -
0
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
-
0
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 fromApplicationService
of ABP. So I did not need to manually createuow
everywhere in the methods. Thank you. -
0
ok, You can try this solution. : )