无法从单例yyy中使用作用域服务xxx

时间:2019-03-24 16:14:30

标签: c# asp.net-core dependency-injection

我关注了一篇博客文章“使用ASP.NET Core和Visual Studio Code构建您的第一个Web API”。

http://www.codingflow.net/building-your-first-web-api-with-asp-net-core-and-visual-studio-code/

在这种情况下,数据不保存在数据库中,而是这样保存:

services.AddDbContext<TodoContext>(options => options.UseInMemoryDatabase());
services.AddSingleton<ITodoRepository, TodoRepository>();

您会注意到:

(1)UseInMemoryDatabase上的DbContext

(2)AddSingleton上的TodoRepository

这很好。现在,我更新了代码以将数据保存在真实数据库中。所以主要的变化是:

services.AddDbContext<TodoContext> (options => options.UseSqlite("Data Source=blogging.db"));            
services.AddSingleton<ITodoRepository, TodoRepository>();

我想通知我,我不得不将AspNetCore从1.0迁移到2.2。

现在在运行时,将控制器作为目标时,出现错误:无法使用单例'Models.ITodoRepository'中的作用域服务'Models.TodoContext'。

我了解这种情况:

  • 我的TodoContext是一个范围对象:请求中的对象相同,但不同请求中的对象不同。

  • 我的TodoRepository是一个Singleton对象:每个对象和每个请求都相同。

所以我终于将AddSingleton更改为AddScoped,效果很好:

services.AddDbContext<TodoContext> (options => options.UseSqlite("Data Source=blogging.db"));            
services.AddScoped<ITodoRepository, TodoRepository>();

我的问题是:要知道这是否可以接受吗?

PS:我知道关于SO的这个问题还有其他问题,但是我没有清楚的回答。

2 个答案:

答案 0 :(得分:0)

对于您的问题,是的,这是正确的做法。

我想以更好的方式总结答案,但是后来我对该主题进行了一些研究,发现an article可以解决您所提出的问题。

我建议您看一下这篇文章,因为该文章包含有关如何解决某些问题的“最佳实践”部分。

答案 1 :(得分:0)

内置于依赖项注入容器中的ASP.NET核心可保护您免受称为“强制性依赖项”的依赖项注入反模式的影响(您可以在一般的here中了解有关它和依赖项注入的更多信息)。

基本思想是,具有一定生存期的类只能依赖具有等于或大于其自身生存期的对象。这是因为当您进行依赖注入时,您为类提供了其所有依赖(通常通过构造函数注入),并且该类保存了对依赖的引用,以便以后可以在需要时使用它。

由于您要设计的类保存对注入对象的引用,因此,只要您的类的实例处于活动状态,注入对象就将保持活动状态(至少)。也许一个例子可以帮助您理解。

public interface IFoo {}

public class Foo: IFoo {}

public class Bar 
{
  private readonly IFoo foo;

  public Bar(IFoo foo)
  {
    this.foo = foo ?? throw new ArgumentNullException(nameof(foo));
  }
}

var foo = new Foo();
var bar = new Bar(foo); // the object referenced by foo variable is alive as long as the Bar instance is alive, because a reference to it is saved inside the private field of Bar instance

如果Foo实例的预期生存期短于Bar实例的预期生存期,则这种情况可能会给您带来麻烦。

例如,想象一下在Web应用程序的上下文中,并假设类Foo不是线程安全的,因此从不同的线程同时访问它的一个实例可能会导致其私有状态的破坏。在这种情况下,您可以决定将Foo类注册为作用域依赖项,以便每次应用程序收到HTTP请求时,都会创建一个新的Foo实例,并且该实例将在HTTP请求的整个生命周期内被重用。这样做很好,即使处理HTTP请求意味着使用了一些异步操作。假设您在等待每个涉及的异步操作,则最多只能有一个线程同时访问Foo实例,并且实例的内部状态得以保留,以防损坏。

在这种情况下,如果将Bar类注册为单例,则在应用程序的整个生命周期中可能只有一个Bar实例,因此不同的线程将同时访问Bar实例(请记住,Web应用程序可以使用线程池同时服务多个请求)。您的Bar的单例实例引用了一个Foo实例,该实例可能会在多个线程中同时使用,这将导致其内部状态损坏并导致无法预测的结果。您有一个圈养依赖性,因为您有一个单例(Bar类),该类依赖于寿命较短的类(范围为Foo的类)。

回到您的问题,您的解决方案是否很好:您不能将存储库注册为单例,因为它必须使用范围内的依赖项,因此我认为将其注册为范围内的服务是很好的方法。