【问题标题】:Cannot consume scoped service IMongoDbContext from singleton IActiveUsersService after upgrade to ASP.NET Core 2.0升级到 ASP.NET Core 2.0 后,无法从单例 IActiveUsersService 使用范围服务 IMongoDbContext
【发布时间】:2018-01-30 08:09:08
【问题描述】:

我今天将一个项目更新为 ASP.NET Core 2,但出现以下错误:

无法使用单例 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 一切正常。

【问题讨论】:

  • 你为什么一开始就想让 Mongo 上下文成为范围?您是否具有某种租户功能,仅在运行时才知道租户 ID? MongoDatabase 是线程安全的,因此您也可以将其保留为单例(假设您没有运行 100 个它保持打开许多连接)。另一方面,EF Core 不是线程安全的,并且默认情况下具有缓存机制(EF Core 跟踪也充当缓存),因此很自然地将其设置为范围)

标签: c# asp.net-core


【解决方案1】:

您不能使用生命周期较短的服务。作用域服务仅针对每个请求存在,而单例服务创建一次并共享实例。

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

您必须:

  1. MongoDbContext 设为单例,或
  2. 使IActiveUsersService 作用域,或
  3. MongoDbContext 作为函数参数传递给用户服务

【讨论】:

  • 有道理,但是,如果我有另一个类,例如 MyService,它是有范围的,我想为这个服务设置范围 MongoDbContext
  • 你不能有不同的生命周期(例如没有不同的接口)。为什么要为那个有一个范围版本?如果一个服务可以是单例的,它应该是单例的。如果不能,但可以限定范围,则应该限定范围。如果它不能被限定,它应该是暂时的。
  • 谢谢,这完全有道理——但我想知道为什么在 ASP.NET Core 1.0 中可以做到这一点?有点混乱。
  • 我认为该错误仅在 2.0 中添加。在某些情况下它可能有效,但基本上你不应该这样做。
【解决方案2】:

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 的引用,每次需要作用域服务时,使用它在using 块内创建一个新的IServiceScope,并在块退出时释放作用域。

请记住,从 IServiceScope 创建的任何 IDisposable 服务都会在作用域本身发生时自动释放。

简而言之,不要仅仅围绕服务的生命周期进行更改以“使其正常工作”;您仍然需要考虑这些并确保它们得到妥善处理。 ASP.NET Core 自动处理最常见的情况;对于其他人,你只需要做更多的工作。

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

【讨论】:

  • BeginScope() 是否等同于 ServiceProviderServiceExtensions.CreateScope()?也许它已被重命名?
  • @gt 是的,不确定它是否被重命名或者我只是输入错误,但我修复了示例并添加了链接,谢谢!
【解决方案3】:

你也可以添加

.UseDefaultServiceProvider(options =>
                    options.ValidateScopes = false)

Program.cs 文件中的.Build() 之前禁用验证。

仅在开发测试中尝试此方法,ActiveUsersService 是单例的,其生命周期比 MongoDbContext 更长,MongoDbContext 是作用域的,不会被释放。

【讨论】:

  • ValidateScopes 的默认值是多少?
  • @JimAho 默认值为 false。我已经成功使用了以下.UseDefaultServiceProvider(options =&gt; { })
【解决方案4】:

还有另一种方法可以解决这个问题,就是将MongoDbContext 添加到 DI 中作为AddTransient,如下所示:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-28
    • 1970-01-01
    • 2021-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多