不能从singleton yyy消耗范围服务xxx

问题描述 投票:0回答:2

我关注了“使用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是一个Scoped对象:在请求中是相同的,但在不同的请求中是不同的。
  • 我的TodoRepository是一个Singleton对象:每个对象和每个请求都是一样的。

所以我最终将AddSingleton改为AddScoped,这非常好用:

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

我的问题是:要知道这是否是一种可接受的方法?

PS:我知道在这个问题上还有其他问题,但我没有看到明确的答复。

c# asp.net-core dependency-injection
2个回答
0
投票

对于你的问题,是的,这是做事情的正确方法。

我想以更好的方式总结答案,但后来我对这个主题进行了一些研究,我发现an article处理的问题就像你问的那样。

我建议看看这篇文章,因为它有关于如何解决某些问题的“最佳实践”部分。


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类)。

回到你的问题你的解决方案是好的:你不能将你的存储库注册为单例,因为它必须使用作用域的依赖项,所以在我看来,将它注册为作用域服务是很好的方法。

© www.soinside.com 2019 - 2024. All rights reserved.