簡體   English   中英

無法在存儲庫構造函數 asp.net core 中創建服務范圍

[英]cannot create scope of services in repository constructor asp.net core

我有瞬態存儲庫服務,每次調用它時都需要創建服務范圍。

我嘗試在存儲庫構造函數中創建此范圍,如下所示:

public class ServiceRepository : IServiceRepository
{
    private IServiceScopeFactory _serviceScopeFactory;
    private IServiceScope _scope;
    private IServiceProvider _serviceContainer;

    private DataBaseContext _db;

    public ServiceRepository(DataBaseContext context, IServiceScopeFactory serviceScopeFactory)
    {
        _db = context;
        _serviceScopeFactory = serviceScopeFactory;
        _scope = _serviceScopeFactory.CreateScope();
        _serviceContainer = _scope.ServiceProvider;
    }

之后我嘗試從服務提供商那里調用我的存儲庫服務:

var serviceRepository = _serviceProvider.GetRequiredService<IServiceRepository>();

我希望每次以這種方式調用此服務時,都會創建我在存儲庫構造函數中聲明的服務范圍。 但是在訪問服務時,我收到錯誤:

System.InvalidOperationException: 'Cannot resolve 'Data_Access_Layer.Interfaces.IServiceRepository' from root provider because it requires scoped service 'Data_Access_Layer.EF.DataBaseContext'.'

我究竟做錯了什么? 之前,我已經設置了這樣的范圍並且它起作用了:

var scopeFactory = _serviceProvider.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
var scopedContainer = scope.ServiceProvider;

但在這種情況下,我需要在每次調用 IServiceRepository 之前聲明范圍。 這就是為什么我想在 IServiceRepository 構造函數中聲明作用域。

我為我提供幫助,只需將.UseDefaultServiceProvider(options => options.ValidateScopes = false)Program.cs BuildWebHos t中,如下所示:

public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .UseDefaultServiceProvider(options => options.ValidateScopes = false)
            .Build();

我希望這也會對某人有用

您正在使用所有這些錯誤。 首先,可以將范圍內的服務直接注入瞬態生存期對象。 應該注入IServiceProviderIServiceScopeFactory等,而是實際的依賴。 已經直接注入了上下文(這是一種作用域服務),因此我不確定為什么要嘗試以其他方式處理任何其他事情。

僅當對象具有單例生存期並且需要范圍服務時,才應注入IServiceProvider (別無其他)。 這被稱為服務定位器反模式,這是一個反模式,原因是:您應該避免盡可能多地執行此操作。 通常,大多數人認為應該是單身的人實際上不應該是單身的人。 在少數情況下,您真正​​需要單身人士的一生。 在所有其他情況下,“范圍”應是您的終生目標。 另外,如果您的單例實際上需要范圍服務,則強烈主張它應該實際上是范圍內的。

但是,如果您確實處在真正需要單身人士生存期並且仍然需要范圍服務的情況下,那么執行此操作的正確方法如下:

public class MySingletonService
{
    private readonly IServiceProvider _provider;

    public MySingletonService(IServiceProvider provider)
    {
        _provider = provider;
    }

    ...
}

就是這樣。 您沒有在構造函數內創建作用域。 從范圍檢索的任何服務僅存在於該范圍內,並且當范圍消失時,該服務也將存在。 因此,您無法將作用域服務持久保存到單例中的ivar。 相反,在需要這種服務的每個單獨方法中,您需要執行以下操作:

using (var scope = _provider.CreateScope())
{
    var myScopedService = scope.ServiceProvider.GetRequiredService<MyScopedService>();
    // do something with scoped service
}

這就是服務定位器是反模式的另一個原因:它導致大量的晦澀和重復的代碼。 有時您別無選擇,但是大多數時候您都會選擇。

雖然 Chris Pratts 的回答提供了“我如何將范圍服務變成單例”的答案,但它並沒有回答為什么您被告知您的存儲庫無法解析(至少在范圍驗證仍在進行時)。

什么是根提供者?

根提供程序是運行時用來通過它創建的范圍來獲取單例服務和所有其他服務的單例提供程序。 它的生命周期與應用程序生命周期直接相關。 如果您編寫 Web api,只要您的應用程序啟動,根提供程序就會存在。

調用 BuildServiceProvider 時會創建根服務提供者。 根服務提供者的生命周期對應應用程序/服務器的生命周期,當提供者與應用程序一起啟動並在應用程序關閉時被銷毀

什么是范圍提供者?

范圍提供程序用於創建……您猜對了,范圍服務。 范圍服務的生命周期與創建它的容器相關聯。

范圍服務由創建它們的容器處理

范圍為您(開發人員)提供了一種定義特定服務生命周期的方法。 在 Web 項目中,范圍的創建通常由請求管道處理,這是大多數場景所需要的。 框架在開始處理請求時為您創建一個范圍,並使用該范圍中的提供者來注入服務。 當請求完成時,作用域與其控制的服務一起被處理。 存在於許多 msdn 文檔中的手動版本如下:

public void DoScopedWork(IServiceProvider serviceProvider)
{
  using (var scope = serviceProvider.CreateScope())
  {
    var scopedProvider = scope.ServiceProvider;
    var myService = scopedProvider.GetService<IMyService>();
    myService.DoWork();
  }
}

來自根提供者的范圍服務?

出於某種原因,默認情況下,開發環境中的范圍驗證處於啟用狀態,這是其中之一,如果您正在閱讀這篇文章,那么您已經做了一些錯誤的功能。

當 ValidateScopes 設置為 true 時,默認服務提供者會執行檢查以驗證:

  • 范圍服務不是從根服務提供者直接或間接解析的
  • 范圍服務不會直接或間接注入單身人士

因為作用域服務由創建它們的提供者在該提供者被釋放(超出...范圍?)時被釋放,所以從根提供者創建一個作用域服務有效地創建了一個單例。

如果在根容器中創建了作用域服務,則該服務的生命周期將有效地提升為單例,因為它僅在應用程序/服務器關閉時由根容器處理。 調用 BuildServiceProvider 時,驗證服務范圍會捕獲這些情況。

這些情況存在驗證,因為在注冊服務時,意圖明確聲明它應該是范圍的。 因此,告訴框架IRepositoryService應該是范圍的,然后告訴它也從根提供程序解析該范圍的服務在術語上是矛盾的。 當然,根提供者可以創建此服務,因此可以關閉驗證作用域的選項,但最好在確定這是正確的做法之前了解這可能對應用程序做什么。

為什么這對 OP 來說是個問題

var serviceRepository = _serviceProvider.GetRequiredService<IServiceRepository>();
@Giacomo,這對您來說是一個問題,根源在於您嘗試使用存儲庫的位置。 無論您在代碼中嘗試解析存儲庫的哪個位置,都沒有為您創建作用域提供程序。 您正在使用根提供程序。 如果沒有關於您實際使用此存儲庫做什么的額外上下文,或者在您一生中這樣做時,我可以說的是您可能需要首先using (var scope = _serviceProvider.CreateScope()) { ... }創建一個范圍using (var scope = _serviceProvider.CreateScope()) { ... }並使用范圍服務提供者來創建您的存儲庫。

您提到您的存儲庫應該是臨時的,但這並不意味着您可以忽略作用域。 每次從提供者那里請求臨時服務都是新的。 從根提供程序請求的瞬態服務仍將是單例。

正確,但不是真的

從技術上講,您對問題的回答可能被認為是正確的,但它仍然沒有了解正在發生的事情。 它沒有解決實際問題,而是忽略警告並讓范圍服務 ( DataBaseContext ) 提升為有效的單例,因為它是由根提供程序創建的,以注入您的存儲庫(也是有效的單例)。 對於在您的情況下使用options.ValidateScopes = false ,我最多可以說它是一種解決方法。
旁注:讓您的數據庫上下文作為單例存在不是一個好主意

引用文件

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM