Reputation: 14520
I'm listening for an incoming Azure service bus message. Following the documentation and receiving the message, I parse the message body and then I want to connect to my DB to edit an entry and then save. But I'm getting this error below when trying to make the call
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
Error
Cannot access a disposed context instance. A common cause of this error is disposing a context instance 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 instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\nObject name: 'DataContext'.
Here is the full service with the method that listens for and picks up incoming Azure messages. Error is on the last line of MessageHandler()
FYI - If I remove the 'await' on the DB call, I still get the same error for a disposed context.
QUESTION - how do I fix this?
public class ServiceBusConsumer : IServiceBusConsumer
{
private readonly IConfiguration _config;
private readonly ServiceBusClient _queueClient;
private readonly ServiceBusProcessor _processor;
private readonly IUnitOfWork _unitOfWork;
private readonly IEventConsumer _eventConsumer;
public ServiceBusConsumer(IConfiguration config, IEventConsumer eventConsumer, IUnitOfWork unitOfWork)
{
_config = config;
_unitOfWork = unitOfWork;
_eventConsumer = eventConsumer;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
public void RegisterOnMessageHandlerAndReceiveMessages() {
_processor.ProcessMessageAsync += MessageHandler;
_processor.ProcessErrorAsync += ErrorHandler;
_processor.StartProcessingAsync();
}
private async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
JObject jsonObject = JObject.Parse(body);
var eventStatus = (string)jsonObject["EventStatus"];
await args.CompleteMessageAsync(args.Message);
var spec = new YogabandEventWithMessageIdSpecification(args.Message.SequenceNumber);
// error here...
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
// do something then save
}
private Task ErrorHandler(ProcessErrorEventArgs args)
{
var error = args.Exception.ToString();
return Task.CompletedTask;
}
}
Here is my unit of work
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class // : BaseEntity
{
if(_repositories == null)
_repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>) _repositories[type];
}
I tried to call my generic repo directly inside the handler but that still fails with the dispose error.
Here is the call I changed in the handler, now I call the gen repo instead of the unit of work
var ybEvent = await _eventsRepo.GetEntityWithSpec(spec);
Here is GetEntityWIthSpec() from my generic repo
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
FYI - here is how I init my repo call
private readonly IGenericRepository<YogabandEvent> _eventsRepo;
then I inject it into the constructor
public ServiceBusConsumer(IConfiguration config, IEventConsumer eventConsumer, IUnitOfWork unitOfWork, IGenericRepository<YogabandEvent> eventsRepo)
{
_config = config;
_unitOfWork = unitOfWork;
_eventConsumer = eventConsumer;
_eventsRepo = eventsRepo;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
Code that starts the ServiceBusConsumer it's in Main()
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
// do some work here
// https://stackoverflow.com/questions/48590579/cannot-resolve-scoped-service-from-root-provider-net-core-2
var bus = services.GetRequiredService<IServiceBusConsumer>();
bus.RegisterOnMessageHandlerAndReceiveMessages();
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "An error occured during migration");
}
}
host.Run();
}
Here is my unit of work
public class UnitOfWork : IUnitOfWork
{
private readonly DataContext _context;
private Hashtable _repositories;
public UnitOfWork(DataContext context)
{
_context = context;
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class // : BaseEntity
{
if(_repositories == null)
_repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>) _repositories[type];
}
}
Upvotes: 1
Views: 682
Reputation: 27282
Remove this dispose from UnitOfWork
:
public void Dispose()
{
_context.Dispose();
}
Simple rule: if you have not created object - do not dispose. It will be disposed automatically when scope diposed.
Also consider to remove this boilerplate. DbContext
is already UoW, DbSet
is already repository.
Upvotes: 2
Reputation: 918
I cannot verify this but I had a similar situation where creating a response call to a service actually disposed my UoW and the datacontext and I faced the same error
I'm suspecting that the this call await args.CompleteMessageAsync(args.Message);
is doing the disposing somewhere between the lines, (you can continue to trace here CompleteMessageAsync calls
this and a lot of disposing is going on)
to verify that, you can try to postpone that call till after you use the repository to save the changes.
// await args.CompleteMessageAsync(args.Message); <-- comment this line
var spec = new YogabandEventWithMessageIdSpecification(args.Message.SequenceNumber);
// error here...
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
// do something then save
await args.CompleteMessageAsync(args.Message); // <-- add it here
Upvotes: 0