1

I use MQTT (https://github.com/dotnet/MQTTnet) in my WebAPI project (.net 6) and need to check in the database when get message from MQTT, but can't and occur 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. Object name: 'DatabaseContext

I use dependency injection and UnitOFWork

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(builder.Configuration.GetSection("ConnectionStrings").GetSection(environment).Value));
builder.Services.AddTransient<IUnitOfWork, UnitOfWork>();
builder.Services.AddTransient<MQTTManager>();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();

MQTTCheck(app);

app.MapControllers();
app.Run();

void MQTTCheck(IHost app)
{
    var scopedFactory = app.Services.GetService<IServiceScopeFactory>();
    using (var scope = scopedFactory.CreateScope())
    {
        var service = scope.ServiceProvider.GetService<MQTTManager>();
        service.check();
    }
}

MQTTManager.cs:

public class MQTTManager
    {
        IMqttClient client;
        readonly IUnitOfWork _uow;

        string serverAddress = "XXXXXX";
        int port = 1883;

        public MQTTManager(IUnitOfWork uow)
        {
            _uow = uow;
        }

        public async void check()
        {
            try
            {
                client = new MqttFactory().CreateMqttClient();
                var options = new MqttClientOptionsBuilder()
                    .WithClientId(Guid.NewGuid().ToString())
                    .WithTcpServer(serverAddress, port)
                    .WithCleanSession()
                    .WithWillRetain(false)
                    .Build();

                client.ApplicationMessageReceivedAsync += Client_ApplicationMessageReceivedAsync;

                await client.ConnectAsync(options);
            }
            catch (Exception ex){}
        }

        private Task Client_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs x)
        {
            string topic = x.ApplicationMessage.Topic;
            string receiveMsg = x.ApplicationMessage.ConvertPayloadToString();

            Home home = _uow.HomeRepository.Get(h => h.Name == topic);
            //...

            return Task.CompletedTask;
        }

    }

The error occurs in this line:

Home home = _uow.HomeRepository.Get(h => h.Name == topic);

For example in windows form application when I want to access controls on the form in MQTT receive message event should use this pattern

this.BeginInvoke((MethodInvoker)delegate { label1.Text = "Disconnected"; });

I think that I should use something like this!

SajjadZare
  • 2,487
  • 4
  • 38
  • 68
  • Which line throws the exception? – Manuel Fabbri Dec 19 '22 at 14:08
  • I feel like details are missing here for us to answer this. But the error is pretty clear. You are trying to access the database using a context that's been disposed already. I feel your dependency injection is not correct. – Timothy G. Dec 19 '22 at 14:11
  • @TimothyG. I can access database in function check before line connectingToMQTT but can't access databse in receive message event – SajjadZare Dec 19 '22 at 14:26
  • @ManuelFabbri In function Client_ApplicationMessageReceivedAsync – SajjadZare Dec 19 '22 at 14:27
  • Do you call `app.WaitForShutdown();` in the Main function? – Manuel Fabbri Dec 19 '22 at 14:31
  • @ManuelFabbri No – SajjadZare Dec 19 '22 at 14:44
  • How do you start the web application then? Is possible that the host is already stopped when you check the MQTT connection? – Manuel Fabbri Dec 19 '22 at 14:47
  • I tested it in my system, the problem is that I can access out of MQTT but can't access in MQTT receive event, for example in windows form application when want to access controls on the form in MQTT receive event should use this pattern: this.BeginInvoke((MethodInvoker)delegate { label1.Text = "Disconnected"; }); – SajjadZare Dec 19 '22 at 14:59
  • @ManuelFabbri I edited the question and add all the code – SajjadZare Dec 19 '22 at 20:41
  • @TimothyG. I edited the question and add all the code – SajjadZare Dec 19 '22 at 20:42
  • `check()` is going to exit as soon as connected. Your `MQTTManager` will then be disposed, along with its UOW, DBContext`, etc, so the exception is expected. – sellotape Dec 19 '22 at 21:17
  • Once you fix that, you also have the issue that DbContext is not thread safe, and you’re going to receive the event on a different thread to the one your manager is created on, so you’ll need to either create a new DBContext in the event handler, or refactor around that. – sellotape Dec 19 '22 at 21:18
  • @sellotape I answered the question, could you check it that is it ok – SajjadZare Dec 20 '22 at 06:49

1 Answers1

0

I can solve the problem with these changes:

I changed this line in Program.cs

builder.Services.AddTransient<MQTTManager>();

to

builder.Services.AddSingleton<MQTTManager>();

In MQTTManager.cs remove IUnitOfWork and access to the database like below

using (var scope = _factory.CreateScope())
{
   var context = scope.ServiceProvider.GetRequiredService<DatabaseContext>(); 
   var _db = new Lazy<DbSet<Home>>(() => context.Set<Home>());
   Home home = _db.Value.Where(x => x.ClientId == topic).FirstOrDefault();
}
SajjadZare
  • 2,487
  • 4
  • 38
  • 68
  • That's just making the `MQTTManager` not get disposed when the DI scope is disposed. You're mixing an ASP web app with your MQTT manager in an unusual way. If the app really is a web app - i.e. it also serves web content - you might typically use a [background service](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) to handle the interaction with the MQTT broker. Then all the `MQTTCheck()` stuff will go away and be contained within the background service. – sellotape Dec 20 '22 at 12:00
  • Also the `Lazy` is serving no purpose there; just use the `Set` directly. Or, more commonly, have a property in your DB context that represents the set, and then do something like `context.Homes.FirstOrDefault(x => x.ClientId == topic)`. – sellotape Dec 20 '22 at 12:06