升级到ASP.NET Core 2.0后,无法从singleton IActiveUsersService使用作用域服务IMongoDbContext

时间:2017-08-22 06:58:01

标签: c# asp.net-core

我今天更新了一个项目到ASP.NET Core 2,我收到以下错误:

  

不能从singleton IActiveUsersService消耗范围服务IMongoDbContext

我有以下注册:

services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddScoped<IMongoDbContext, MongoDbContext>();
services.AddSingleton(option =>
{
   var client = new MongoClient(MongoConnectionString.Settings);
   return client.GetDatabase(MongoConnectionString.Database);
})



public class MongoDbContext : IMongoDbContext
{
   private readonly IMongoDatabase _database;

   public MongoDbContext(IMongoDatabase database)
   {
      _database = database;
   }

   public IMongoCollection<T> GetCollection<T>() where T : Entity, new()
   {
      return _database.GetCollection<T>(new T().CollectionName);
   }
}

public class IActiveUsersService: ActiveUsersService
{

   public IActiveUsersService(IMongoDbContext mongoDbContext)
   {
      ...
   }
}

为什么DI无法使用该服务?一切都适用于ASP.NET Core 1.1。

4 个答案:

答案 0 :(得分:39)

您无法使用寿命较短的服务。每个请求只存在范围内的服务,而单例服务只创建一次并且实例是共享的。

现在,应用中只存在IActiveUsersService的一个实例。但它希望依赖于MongoDbContext,它是Scoped,并且是按请求创建的。

您必须:

  1. MongoDbContext成为单身人士,或
  2. 制作IActiveUsersService Scoped,或
  3. MongoDbContext作为函数参数传递给用户服务

答案 1 :(得分:11)

Scoped和Singleton服务之间存在重要差异。警告可以解决这个问题,并将其关闭或不加选择地切换生命周期以使其消失将无法解决问题

范围服务是从IServiceScope创建的。其最重要的目的之一是确保在范围本身时正确处理在该范围内创建的任何IDisposable服务。

在ASP.NET Core中,会在每个传入请求中自动为您创建服务范围,因此您通常不必担心这一点。但是,您也可以创建自己的服务范围;你只需要自己处理它。

这样做的一种方法是:

  • 制作您的单身人士服务IDisposable
  • 注入IServiceProvider
  • 使用IServiceProvider.CreateScope()扩展程序
  • 创建并存储IServiceScope范围
  • 使用该范围来创建所需的范围服务
  • 将服务范围置于Dispose方法中。
services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddScoped<IMongoDbContext, MongoDbContext>();
services.AddSingleton(option =>
{
   var client = new MongoClient(MongoConnectionString.Settings);
   return client.GetDatabase(MongoConnectionString.Database);
})

public class MongoDbContext : IMongoDbContext
{
   private readonly IMongoDatabase _database;

   public MongoDbContext(IMongoDatabase database)
   {
      _database = database;
   }

   public IMongoCollection<T> GetCollection<T>() where T : Entity, new()
   {
      return _database.GetCollection<T>(new T().CollectionName);
   }
}

public class ActiveUsersService: IActiveUsersService, IDisposable
{
   private readonly IServiceScope _scope;

   public ActiveUsersService(IServiceProvider services)
   {
      _scope = services.CreateScope(); // CreateScope is in Microsoft.Extensions.DependencyInjection
   }

   public IEnumerable<Foo> GetFooData()
   {
       using (var context = _scope.ServiceProvider.GetRequiredService<IMongoDbContext>())
       {
           return context.GetCollection<Foo>();
       }
   }

   public void Dispose()
   {
       _scope?.Dispose();
   }
}

根据您使用这些以及您正在使用的范围服务的方式,您可以改为执行以下操作之一:

  • 创建作用域服务的单个实例,并将其用于单例的生命周期;或
  • 存储对(注入的)根IServiceProvider的引用,每次需要作用域服务时,使用它在IServiceScope块内创建一个新的using,让范围得到在街区退出时处理。

请记住,从IDisposable创建的任何IServiceScope服务都会在范围本身时自动处理。

简而言之,不要只是改变服务的生命周期来“让它工作”;你还需要考虑那些并确保它们得到妥善处理。 ASP.NET Core自动处理最常见的情况;对于其他人,你只需要做更多的工作。

自从C#1.0以来,我们已经有using()块来确保正确处理资源。但是当其他东西(DI服务)正在为您创建这些资源时,using()块不起作用。这就是Scoped服务的用武之地,错误地使用它们会导致程序中的资源泄漏。

答案 2 :(得分:3)

您也可以添加

.UseDefaultServiceProvider(options =>
                    options.ValidateScopes = false)
.Build()文件中的Program.cs之前

以禁用验证。

尝试仅用于开发测试,ActiveUsersService是单例,并且具有比MongoDbContext更大的生命周期,MongoDbContext是作用域的,不会被处理掉。

答案 3 :(得分:1)

还有另一种解决此问题的方法,就是将MongoDbContext添加到DI AddTransient,如下所示:

services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddTransient<IMongoDbContext, MongoDbContext>();

使用此方法的含义是,对于您使用它的每个MongoDbContext类,最终会得到Singleton的实例。 例如,如果您使用MongoDbContext有10个Singleton类,则它将有10个实例,但它不是为每个请求创建实例。

请参阅此参考:Cannot Consume Scoped Service From Singleton – A Lesson In ASP.net Core DI Scopes