我正在使用Dot.Net Core 2和EntityFramework 2.0.1以及M2Mqtt Client(M2MqttDotnetCore 1.0.7)。当M2Mqtt客户端收到内容时,它应将其存储到数据库中,但此时会抛出以下错误:
System.ObjectDisposedException:“无法访问已处置的对象。
我可以从控制器调用存储库,它工作正常。如何让它在Mqtt事件处理程序中工作?我错过了什么?
public class MyRepository : IRepository
{
private readonly MyContext _context;
public MyRepository(MyContext context)
{
_context = context;
}
public void AddMyItem(Item item)
{
_context.Items.Add(item) //Throws error at this line
}
}
Startup.cs文件包含
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
services.AddScoped<IRepository, MyRepository>();
services.AddScoped<IMqttApiService, MqttApiService>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
MyContext myContext, IServiceProvider serviceProvider)
{
...
app.UseMvc();
mqttApiService = serviceProvider.GetService<IMqttApiService>();
mqttApiService.Start();
}
}
MqttApiService.cs
public class MqttApiService : IMqttApiService
{
private MqttClient _client;
private readonly IRepository _repo;
public MqttApiService(IRepository repository)
{
_repo = repository;
}
~MqttApiService()
{
_client.Disconnect();
}
public void Start()
{
string hostname = "localhost";
_client = new MqttClient(hostname);
_client.MqttMsgPublishReceived += client_MqttMsgPublishReceived;
string clientId = Guid.NewGuid().ToString();
var qosLevels = new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE };
var topics = new String[] { "/home/temperature" };
_client.Subscribe(topics, qosLevels);
_client.Connect(clientId);
}
private void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
string result = System.Text.Encoding.UTF8.GetString(e.Message);
Console.WriteLine(result);
Item item = new Item {name = result};
_repo.AddMyItem(item);
}
}
答案 0 :(得分:1)
将DbContext
的实例注入存储库类并重复使用它以进行不同的操作。这是DbContext
的错误用法。正确的方法是在每个操作中创建DbContext
的实例,并在使用后立即处理它。
如果您尝试使用来自多个同时线程的存储库,则除了ObjectDisposedException
之外,您可能面临的另一个问题是NotSupportedException
。 DbContext
类只是不支持同一个实例上的同时操作。有关此问题,请参阅我的answer。
不要因为每个上下文创建的连接打开而担心性能下降。实体框架是在ADO.NET上构建的,它使用连接池。与Singleton上下文相比,性能不会降低。
此处更改AddMyItem()
方法的示例:
public void AddMyItem(Item item)
{
using (var context = new MyContext())
{
context.Items.Add(item);
context.SaveChanges();
}
}
答案 1 :(得分:0)
我可以从控制器调用存储库,它工作正常。如何让它在Mqtt事件处理程序中工作?
您注册了要在HTTP请求范围中注入的存储库。如果您在请求完成后以某种方式访问它,您应该会遇到此错误。因此,在控制器启动MQTT服务之后,它会在作用域服务上调用Dispose。唯一的原因就是它的工作原理是MQTT服务不是IDisposable,而是有一个终结器。
看起来这两种服务都应该更改DI注册。 EG MQTT服务是一个单例,以及存储库瞬态。然后,MQTT服务将管理存储库实例的生命周期。
答案 2 :(得分:0)
我找到了一个不需要我更改存储库并将所有内容保持在作用域模式的解决方案。
我首先更改了添加IServiceProvider的MqttApiService:
private readonly IServiceProvider _serviceProvider;
public MqttApiService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
然后我使用ServiceProvider中的GetService来获取注入的存储库
private void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
_repo = _serviceProvider.GetService(typeof(IRepository) as IRepository
string result = System.Text.Encoding.UTF8.GetString(e.Message);
Item item = new Item {name = result};
_repo.AddMyItem(item);
}
此解决方案似乎有效。