JC9*_*C97 3 c# dependency-injection asp.net-core
我正在使用 .NET Core 2.1 和实体框架制作一个 n 层 MVC 应用程序。还有一个托管 MQTT 队列,我的应用程序作为客户端进行侦听。我还使用依赖注入。这非常有效,直到一条消息被推送到队列并且我想将该消息保存到数据库。一旦发生这种情况,我会收到以下ObjectDisposedException错误消息:
无法访问已处置的对象。导致此错误的一个常见原因是处置从依赖项注入解析的上下文,然后尝试在应用程序的其他位置使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。如果您使用依赖项注入,则应该让依赖项注入容器负责处理上下文实例。对象名称:“xxxDbContext”。
我可以单击“继续”,之后应用程序将继续工作。他只在从队列收到的第一条消息上抛出异常。控制器/管理器/存储库的所有其他操作都可以正常工作。我的代码如下:
启动.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDefaultIdentity<User>()
.AddEntityFrameworkStores<xxxDbContext>();
services.AddDbContext<xxxDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
));
// Some identity configuration omitted here
services.AddScoped<IIdeationRepository, IdeationRepository>();
services.AddScoped<IIdeationManager, IdeationManager>();
// Some other DI configuration omitted as well.
}
public Configure(IApplicationBuilder app, IHostingEnvironment env,
IApplicationLifetime applicationLifetime, IServiceProvider serviceProvider)
{
// Start MQTT
var broker = new MqttBroker(serviceProvider.GetService<IIdeationManager>(),
serviceProvider.GetService<IConfiguration>());
// On application exit terminate MQTT to make sure the connection is ended properly
applicationLifetime.ApplicationStopping.Register(() => broker.Terminate());
// Some default http pipeline code omitted
}
Run Code Online (Sandbox Code Playgroud)
MqttBroker.cs
public MqttBroker(
[FromServices] IIdeationManager ideationManage,
[FromServices] IConfiguration configuration)
{
_ideationManager = ideationManager;
_configuration = configuration;
Initialize();
}
// Some code where I just parse the message and on receive send it to the
// ideation manager, this just works so I omitted it.
}
Run Code Online (Sandbox Code Playgroud)
管理器只是将其直接发送到发生错误消息的存储库。
存储库.cs
private xxxDbContext ctx;
public IdeationRepository(xxxDbContext xxxDbContext)
{
this.ctx = xxxDbContext;
}
// This method crashes with the error
public IdeationReply ReadIdeationReply(int id)
{
return ctx
.IdeationReplies
.Include(r => r.Votes)
.FirstOrDefault(r => r.IdeationReplyId == id);
}
Run Code Online (Sandbox Code Playgroud)
数据库上下文.cs
public class xxxDbContext : IdentityDbContext<User>
{
public DbSet<Ideation> Ideations { get; set; }
// Some more dbsets omitted
public CityOfIdeasDbContext(DbContextOptions<CityOfIdeasDbContext> options)
: base (options)
{
CityOfIdeasDbInitializer.Initialize(this, dropCreateDatabase: false);
}
// In configuring I just create a logger, nothing special
// In OnModelCreating I just setup some value converters for other tables
// than the ones I need here
internal int CommitChanges()
{
if (delaySave)
{
int infectedRecords = base.SaveChanges();
return infectedRecords;
}
throw new InvalidOperationException(
"No UnitOfWork present, use SaveChanges instead");
}
}
Run Code Online (Sandbox Code Playgroud)
我读过这篇文章,但这些情况似乎都不适合我。当我打印堆栈跟踪时,Dispose()它发生在Main()方法中,所以它并没有真正帮助我。
有人知道如何解决或者我可以在哪里搜索来解决这个问题吗?
提前致谢!
IServiceProvider传入的实例是Configurescoped的,这意味着它在完成后由框架处置Configure- 它创建的任何范围服务也会在此过程中处置。
在您的示例中,您请求一个实例IIdeationManager(其范围是),然后尝试在您的类中使用它MqttBroker(实际上是一个singleton)。当您尝试使用 的实现时IIdeationManager,由 DI 创建和连接的作用域实例已被释放,因此会引发异常。CityOfIdeasDbContextObjectDisposedException
为了解决这个问题,您可以采用当单例需要访问作用域服务时使用的通用模式:创建作用域,解析服务,使用服务,然后释放作用域。粗略地说,这看起来有点像这样:
using (var scope = serviceProvider.CreateScope())
{
var ideationManager = scope.ServiceProvider.GetService<IIdeationManager>();
// Do something with ideationManager.
}
// scope and all created disposable services have been disposed.
Run Code Online (Sandbox Code Playgroud)
当您请求实现 时IIdeationManager,DI 系统(最终)发现它需要一个范围CityOfIdeasDbContext并为您创建一个范围。一旦scope被处置,该CityOfIdeasDbContext实例也被处置。
为了使其在您的示例中工作,您MqttBroker可以将 的一个实例IServiceProvider放入其构造函数中,并使用它来创建我上面显示的范围(IConfiguration鉴于它本身是单例,它仍然可以按原样使用)。
IServiceProvider应该传递到MqttBroker类中的实例不应该是传递到IServiceProvider的实例Configure- 这已经是范围内的,并且正如我所描述的,将在完成后被清理Configure,这确实是您开始遇到的问题。为此,请使用app.ApplicationServices,它是根提供程序且不受作用域限制。