C# ASP.Net.Core 2应用程序的M2Mqtt事件处理程序中的ObjectDisposedException

C# ASP.Net.Core 2应用程序的M2Mqtt事件处理程序中的ObjectDisposedException,c#,entity-framework,asp.net-core,mqtt,core,C#,Entity Framework,Asp.net Core,Mqtt,Core,我在EntityFramework 2.0.1和M2Mqtt客户机(M2MqttDotnetCore 1.0.7)中使用点.Net核心2。当M2Mqtt客户端接收到内容时,它应该将其存储到数据库中,但此时会抛出以下错误: System.ObjectDisposedException:“无法访问已处置的对象 我可以从控制器调用存储库,它工作正常。如何让它在Mqtt事件处理程序中工作?我缺少什么 public class MyRepository : IRepository { private

我在EntityFramework 2.0.1和M2Mqtt客户机(M2MqttDotnetCore 1.0.7)中使用点.Net核心2。当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();
        }
}

您将
DbContext
的实例注入到存储库类中,并在不同的操作中重复使用它。这是
DbContext
的不正确用法。正确的方法是在每个操作中创建
DbContext
的实例,并在使用后立即处理它

除了
ObjectDisposedException
之外,您可能面临的另一个问题是
NotSupportedException
,如果您尝试从多个同时执行的线程使用存储库。类
DbContext
只是不支持在同一实例上同时执行操作。有关此问题,请参阅此处的my

不要害怕由于每次创建上下文都打开连接而导致性能下降。实体框架是在使用连接池的ADO.NET上构建的。与单例上下文相比,性能不应下降

下面是更改的
AddMyItem()
方法的示例:

public void AddMyItem(Item item)
{
    using (var context = new MyContext())
    {
        context.Items.Add(item);
        context.SaveChanges();
    }
}

您将
DbContext
的实例注入到存储库类中,并在不同的操作中重复使用它。这是
DbContext
的不正确用法。正确的方法是在每个操作中创建
DbContext
的实例,并在使用后立即处理它

除了
ObjectDisposedException
之外,您可能面临的另一个问题是
NotSupportedException
,如果您尝试从多个同时执行的线程使用存储库。类
DbContext
只是不支持在同一实例上同时执行操作。有关此问题,请参阅此处的my

不要害怕由于每次创建上下文都打开连接而导致性能下降。实体框架是在使用连接池的ADO.NET上构建的。与单例上下文相比,性能不应下降

下面是更改的
AddMyItem()
方法的示例:

public void AddMyItem(Item item)
{
    using (var context = new MyContext())
    {
        context.Items.Add(item);
        context.SaveChanges();
    }
}
我可以从控制器调用存储库,它工作正常。如何让它在Mqtt事件处理程序中工作

您注册了要注入HTTP请求作用域的存储库。如果您在请求完成后以某种方式访问它,则应该会出现此错误。因此,在控制器启动MQTT服务后,它会调用作用域服务上的Dispose。这一功能正常工作的唯一原因是MQTT服务无法IDisposable,而是有一个终结器

看起来这两个服务的DI注册都应该更改。例如MQTT服务是单例的,存储库是瞬态的。然后MQTT服务将管理存储库实例的生命周期

我可以从控制器调用存储库,它工作正常。如何让它在Mqtt事件处理程序中工作

您注册了要注入HTTP请求作用域的存储库。如果您在请求完成后以某种方式访问它,则应该会出现此错误。因此,在控制器启动MQTT服务后,它会调用作用域服务上的Dispose。这一功能正常工作的唯一原因是MQTT服务无法IDisposable,而是有一个终结器


看起来这两个服务的DI注册都应该更改。例如MQTT服务是单例的,而存储库是临时的。然后MQTT服务将管理存储库实例的生命周期。

我找到了一个解决方案,它不需要我更改存储库并将所有内容保持在范围模式

我首先更改了添加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);
}

这个解决方案似乎有效。

我找到了一个解决方案,它不需要我更改存储库并将所有内容保持在范围模式

我首先更改了添加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);
}

此解决方案似乎有效。

这会导致System.InvalidOperationException:“无法使用singleton IMqttApiService提供的作用域服务'Microsoft.EntityFrameworkCore.DbContextOptions'“尽管我使用ServiceLifetime.Transient参数调用了AddDbContext,并对存储库使用了AddTransient。这会导致System.InvalidOperationException:“无法从singleton IMqttApiService使用作用域服务'Microsoft.EntityFrameworkCore.DbContextOptions'”尽管我使用ServiceLifetime.Transient参数调用了AddDbContext,并在存储库中使用了AddTransient。这虽然有效,但却破坏了封装,并破坏了使用依赖注入的目的。存储库也是一个单独的库,无法访问重新实例化上下文所需的连接字符串。(var builder=new DbContextOptionsBuilder();builder.UseSqlServer(connectionString);using(var context=new MyContext(builder.Options))…如果这对您来说是个问题,您可以将某种DbContext工厂传递给构造函数。它可以是单独的接口(
IContextFactory
)或者像
Func
这样简单的东西。检查这个,它详细描述了管理DbContext生存期的不同方法及其优缺点。感谢这一点,这非常有用,因此我标记了这个答案。今天上午,在尝试了答案和不同的东西之后,我最终找到了一个解决方案似乎可以在不必更改存储库和保存所有内容的情况下工作