ASP.NET Core 相依性 Blazor 插入ASP.NET Core Blazor dependency injection

By Rainer StropekMike RousosBy Rainer Stropek and Mike Rousos

Blazor支援相依性插入(DI) supports dependency injection (DI). 應用程式可以使用內建的服務,方法是將它們插入元件中。Apps can use built-in services by injecting them into components. 應用程式也可以定義和註冊自訂服務,並透過 DI 讓它們可在整個應用程式中使用。Apps can also define and register custom services and make them available throughout the app via DI.

DI 是用來存取集中位置所設定之服務的技術。DI is a technique for accessing services configured in a central location. 這在應用程式中相當實用 Blazor ,可用於:This can be useful in Blazor apps to:

  • 跨許多元件(稱為單一服務)共用服務類別的單一實例。Share a single instance of a service class across many components, known as a singleton service.
  • 使用參考抽象,將元件與具體的服務類別分離。Decouple components from concrete service classes by using reference abstractions. 例如,請考慮使用介面 IDataAccess 來存取應用程式中的資料。For example, consider an interface IDataAccess for accessing data in the app. 介面是由具 DataAccess 象類別來執行,並在應用程式的服務容器中註冊為服務。The interface is implemented by a concrete DataAccess class and registered as a service in the app's service container. 當元件使用 DI 來接收實作為時 IDataAccess ,該元件不會結合到具體類型。When a component uses DI to receive an IDataAccess implementation, the component isn't coupled to the concrete type. 可以交換執行,也許是針對單元測試中的 mock 執行。The implementation can be swapped, perhaps for a mock implementation in unit tests.

預設服務Default services

預設服務會自動新增至應用程式的服務集合。Default services are automatically added to the app's service collection.

| 服務Service | 存留期Lifetime | 說明Description | | --- 標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'
  • 'Identity''Identity'
  • 'Let's Encrypt''Let's Encrypt'
  • 'Razor''Razor'
  • ' SignalR ' uid:'SignalR' uid:

---- |---標題: ' ASP.NET Core 相依性 Blazor 插入 ' 作者:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。---- | --- title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'

  • 'Identity''Identity'

  • 'Let's Encrypt''Let's Encrypt'

  • 'Razor''Razor'

  • ' SignalR ' uid:'SignalR' uid:

標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'
  • 'Identity''Identity'
  • 'Let's Encrypt''Let's Encrypt'
  • 'Razor''Razor'
  • ' SignalR ' uid:'SignalR' uid:

---- |---標題: ' ASP.NET Core 相依性 Blazor 插入 ' 作者:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。---- | --- title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'

  • 'Identity''Identity'

  • 'Let's Encrypt''Let's Encrypt'

  • 'Razor''Razor'

  • ' SignalR ' uid:'SignalR' uid:

標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'

  • 'Identity''Identity'

  • 'Let's Encrypt''Let's Encrypt'

  • 'Razor''Razor'

  • ' SignalR ' uid:'SignalR' uid:

標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'
  • 'Identity''Identity'
  • 'Let's Encrypt''Let's Encrypt'
  • 'Razor''Razor'
  • ' SignalR ' uid:'SignalR' uid:

------ | |HttpClient |暫時性 |提供方法來傳送 HTTP 要求,以及從 URI 所識別的資源接收 HTTP 回應。------ | | HttpClient | Transient | Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.

HttpClientWebAssembly 應用程式中的實例會 Blazor 使用瀏覽器來處理背景中的 HTTP 流量。The instance of HttpClient in a Blazor WebAssembly app uses the browser for handling the HTTP traffic in the background.

Blazor伺服器應用程式預設不包含 HttpClient 已設定為服務的。 Server apps don't include an HttpClient configured as a service by default. 將提供 HttpClient 給 Blazor 伺服器應用程式。Provide an HttpClient to a Blazor Server app.

如需詳細資訊,請參閱<xref:blazor/call-web-api>。For more information, see <xref:blazor/call-web-api>. | |IJSRuntime |Singleton ( Blazor WebAssembly)| | IJSRuntime | Singleton (Blazor WebAssembly)
限定範圍( Blazor 伺服器) |代表在其中分派 JavaScript 呼叫的 JavaScript 執行時間實例。Scoped (Blazor Server) | Represents an instance of a JavaScript runtime where JavaScript calls are dispatched. 如需詳細資訊,請參閱<xref:blazor/call-javascript-from-dotnet>。For more information, see <xref:blazor/call-javascript-from-dotnet>. | |NavigationManager |Singleton ( Blazor WebAssembly)| | NavigationManager | Singleton (Blazor WebAssembly)
限定範圍( Blazor 伺服器) |包含使用 Uri 和導覽狀態的協助程式。Scoped (Blazor Server) | Contains helpers for working with URIs and navigation state. 如需詳細資訊,請參閱URI 和流覽狀態協助程式。For more information, see URI and navigation state helpers. |

自訂服務提供者不會自動提供表格中所列的預設服務。A custom service provider doesn't automatically provide the default services listed in the table. 如果您使用自訂服務提供者,而且需要資料表中所顯示的任何服務,請將所需的服務新增至新的服務提供者。If you use a custom service provider and require any of the services shown in the table, add the required services to the new service provider.

將服務新增至應用程式Add services to an app

BlazorWebAssembly WebAssembly

在 Program.cs 的方法中,為應用程式的服務集合設定服務 MainProgram.csConfigure services for the app's service collection in the Main method of Program.cs. 在下列範例中, MyDependency 會為 IMyDependency 執行註冊:In the following example, the MyDependency implementation is registered for IMyDependency:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<IMyDependency, MyDependency>();
        builder.RootComponents.Add<App>("app");

        await builder.Build().RunAsync();
    }
}

建立主機之後,您就可以從根 DI 範圍存取服務,然後再呈現任何元件。Once the host is built, services can be accessed from the root DI scope before any components are rendered. 這有助於在轉譯內容之前執行初始化邏輯:This can be useful for running initialization logic before rendering content:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync();

        await host.RunAsync();
    }
}

主機也會提供應用程式的中央設定實例。The host also provides a central configuration instance for the app. 以先前的範例為基礎,氣象服務的 URL 會從預設設定來源(例如appsettings)傳遞至 InitializeWeatherAsyncBuilding on the preceding example, the weather service's URL is passed from a default configuration source (for example, appsettings.json) to InitializeWeatherAsync:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync(
            host.Configuration["WeatherServiceUrl"]);

        await host.RunAsync();
    }
}

Blazor伺服器 Server

建立新的應用程式之後,請檢查 Startup.ConfigureServices 方法:After creating a new app, examine the Startup.ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    // Add custom services here
}

ConfigureServices會傳遞方法 IServiceCollection ,這是服務描述元物件()的清單 ServiceDescriptorThe ConfigureServices method is passed an IServiceCollection, which is a list of service descriptor objects (ServiceDescriptor). 服務會藉由將服務描述項提供給服務集合來新增。Services are added by providing service descriptors to the service collection. 下列範例示範 IDataAccess 介面和其具體執行的概念 DataAccessThe following example demonstrates the concept with the IDataAccess interface and its concrete implementation DataAccess:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataAccess, DataAccess>();
}

服務存留期Service lifetime

您可以使用下表所示的存留期來設定服務。Services can be configured with the lifetimes shown in the following table.

| 存留期Lifetime | 說明Description | | --- 標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'

  • 'Identity''Identity'

  • 'Let's Encrypt''Let's Encrypt'

  • 'Razor''Razor'

  • ' SignalR ' uid:'SignalR' uid:

標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'
  • 'Identity''Identity'
  • 'Let's Encrypt''Let's Encrypt'
  • 'Razor''Razor'
  • ' SignalR ' uid:'SignalR' uid:

---- |---標題: ' ASP.NET Core 相依性 Blazor 插入 ' 作者:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。---- | --- title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'

  • 'Identity''Identity'

  • 'Let's Encrypt''Let's Encrypt'

  • 'Razor''Razor'

  • ' SignalR ' uid:'SignalR' uid:

標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'

  • 'Identity''Identity'

  • 'Let's Encrypt''Let's Encrypt'

  • 'Razor''Razor'

  • ' SignalR ' uid:'SignalR' uid:

標題: ' ASP.NET Core 相依性 Blazor 插入 ' author:描述: ' 瞭解 Blazor 應用程式如何將服務插入元件中。title: 'ASP.NET Core Blazor dependency injection' author: description: 'See how Blazor apps can inject services into components.' monikerRange: ms-chap: ms. custom: ms. date: no-loc:monikerRange: ms.author: ms.custom: ms.date: no-loc:

  • 'Blazor''Blazor'
  • 'Identity''Identity'
  • 'Let's Encrypt''Let's Encrypt'
  • 'Razor''Razor'
  • ' SignalR ' uid:'SignalR' uid:

------ | |Scoped | BlazorWebAssembly apps 目前不具有 DI 範圍的概念。------ | | Scoped | Blazor WebAssembly apps don't currently have a concept of DI scopes. Scoped註冊的服務的行為就像 Singleton 服務一樣。Scoped-registered services behave like Singleton services. 不過, Blazor 伺服器裝載模型支援 Scoped 存留期。However, the Blazor Server hosting model supports the Scoped lifetime. 在 Blazor 伺服器應用程式中,範圍服務註冊的範圍是連接In Blazor Server apps, a scoped service registration is scoped to the connection. 因此,即使目前的意圖是在瀏覽器中執行用戶端,使用範圍服務也適用于應該範圍設定為目前使用者的服務。For this reason, using scoped services is preferred for services that should be scoped to the current user, even if the current intent is to run client-side in the browser. | |Singleton |DI 會建立服務的單一實例| | Singleton | DI creates a single instance of the service. 所有需要服務的元件 Singleton 都會收到相同服務的實例。All components requiring a Singleton service receive an instance of the same service. | |Transient |每當元件 Transient 從服務容器取得服務的實例時,就會收到服務的新實例| | Transient | Whenever a component obtains an instance of a Transient service from the service container, it receives a new instance of the service. |

DI 系統是以 ASP.NET Core 中的 DI 系統為基礎。The DI system is based on the DI system in ASP.NET Core. 如需詳細資訊,請參閱.NET Core 中的相依性插入For more information, see .NET Core 中的相依性插入.

要求元件中的服務Request a service in a component

將服務新增至服務集合之後,請使用 @ 插入指示詞將服務插入元件中 Razor 。After services are added to the service collection, inject the services into the components using the @inject Razor directive. @inject有兩個參數:@inject has two parameters:

  • 輸入 – 要插入之服務的類型。Type – The type of the service to inject.
  • 屬性 – 接收插入的應用程式服務之屬性的名稱。Property – The name of the property receiving the injected app service. 屬性不需要手動建立。The property doesn't require manual creation. 編譯器會建立屬性。The compiler creates the property.

如需詳細資訊,請參閱ASP.NET Core 檢視中的相依性插入For more information, see ASP.NET Core 檢視中的相依性插入.

使用多個 @inject 語句來插入不同的服務。Use multiple @inject statements to inject different services.

下列範例示範如何使用 @injectThe following example shows how to use @inject. 執行的服務 Services.IDataAccess 會插入元件的屬性中 DataRepositoryThe service implementing Services.IDataAccess is injected into the component's property DataRepository. 請注意程式碼如何使用 IDataAccess 抽象概念:Note how the code is only using the IDataAccess abstraction:

@page "/customer-list"
@using Services
@inject IDataAccess DataRepository

@if (customers != null)
{
    <ul>
        @foreach (var customer in customers)
        {
            <li>@customer.FirstName @customer.LastName</li>
        }
    </ul>
}

@code {
    private IReadOnlyList<Customer> customers;

    protected override async Task OnInitializedAsync()
    {
        customers = await DataRepository.GetAllCustomersAsync();
    }
}

就內部而言,產生的屬性( DataRepository )會使用 InjectAttribute 屬性。Internally, the generated property (DataRepository) uses the InjectAttribute attribute. 通常不會直接使用這個屬性。Typically, this attribute isn't used directly. 如果元件需要基類,而且基類也需要插入的屬性,請手動新增 InjectAttributeIf a base class is required for components and injected properties are also required for the base class, manually add the InjectAttribute:

public class ComponentBase : IComponent
{
    // DI works even if using the InjectAttribute in a component's base class.
    [Inject]
    protected IDataAccess DataRepository { get; set; }
    ...
}

在衍生自基類的元件中, @inject 不需要指示詞。In components derived from the base class, the @inject directive isn't required. InjectAttribute基類的已足夠:The InjectAttribute of the base class is sufficient:

@page "/demo"
@inherits ComponentBase

<h1>Demo Component</h1>

在服務中使用 DIUse DI in services

複雜的服務可能需要額外的服務。Complex services might require additional services. 在先前的範例中, DataAccess 可能需要 HttpClient 預設服務。In the prior example, DataAccess might require the HttpClient default service. @inject(或 InjectAttribute )無法在服務中使用。@inject (or the InjectAttribute) isn't available for use in services. 必須改為使用函數插入Constructor injection must be used instead. 將參數新增至服務的函式,即可加入必要的服務。Required services are added by adding parameters to the service's constructor. 當 DI 建立服務時,它會辨識它在此函式中所需的服務,並據以提供它們。When DI creates the service, it recognizes the services it requires in the constructor and provides them accordingly.

public class DataAccess : IDataAccess
{
    // The constructor receives an HttpClient via dependency
    // injection. HttpClient is a default service.
    public DataAccess(HttpClient client)
    {
        ...
    }
}

函式插入的必要條件:Prerequisites for constructor injection:

  • 其中一個函式必須存在,且其引數可由 DI 完成。One constructor must exist whose arguments can all be fulfilled by DI. 如果指定預設值,則允許 DI 未涵蓋的其他參數。Additional parameters not covered by DI are allowed if they specify default values.
  • 適用的函式必須是公用的。The applicable constructor must be public.
  • 其中一個適用的函數必須存在。One applicable constructor must exist. 如果發生不明確的情況,DI 會擲回例外狀況。In case of an ambiguity, DI throws an exception.

用來管理 DI 範圍的公用程式基底元件類別Utility base component classes to manage a DI scope

在 ASP.NET Core 應用程式中,限域服務的範圍通常是目前的要求。In ASP.NET Core apps, scoped services are typically scoped to the current request. 要求完成之後,DI 系統會處置任何範圍或暫時性的服務。After the request completes, any scoped or transient services are disposed by the DI system. 在 [ Blazor 伺服器應用程式] 中,要求範圍會在用戶端連線期間持續進行,這可能會導致暫時性和範圍服務的存留時間比預期長。In Blazor Server apps, the request scope lasts for the duration of the client connection, which can result in transient and scoped services living much longer than expected. 在 Blazor WebAssembly apps 中,以限定範圍存留期註冊的服務會視為單次個體,因此其存留時間比一般 ASP.NET Core 應用程式中的限域服務長。In Blazor WebAssembly apps, services registered with a scoped lifetime are treated as singletons, so they live longer than scoped services in typical ASP.NET Core apps.

限制應用程式中服務存留期的方法 Blazor 是使用 OwningComponentBase 類型。An approach that limits a service lifetime in Blazor apps is use of the OwningComponentBase type. OwningComponentBase是衍生自的抽象類別型 ComponentBase ,它會建立與元件存留期相對應的 DI 範圍。OwningComponentBase is an abstract type derived from ComponentBase that creates a DI scope corresponding to the lifetime of the component. 使用此範圍時,您可以使用具有限定範圍存留期的 DI 服務,而且只要元件,就可以讓它們正常運作。Using this scope, it's possible to use DI services with a scoped lifetime and have them live as long as the component. 當元件損毀時,來自元件範圍服務提供者的服務也會一併處置。When the component is destroyed, services from the component's scoped service provider are disposed as well. 這適用于下列服務:This can be useful for services that:

  • 應該在元件內重複使用,因為暫時性存留期並不適當。Should be reused within a component, as the transient lifetime is inappropriate.
  • 不應跨元件共用,因為單一存留期並不適當。Shouldn't be shared across components, as the singleton lifetime is inappropriate.

類型的兩個版本 OwningComponentBase 可供使用:Two versions of the OwningComponentBase type are available:

  • OwningComponentBase是類型的抽象、可處置子系, ComponentBase 具有類型的受保護 ScopedServices 屬性 IServiceProviderOwningComponentBase is an abstract, disposable child of the ComponentBase type with a protected ScopedServices property of type IServiceProvider. 這個提供者可以用來解析範圍設定為元件存留期的服務。This provider can be used to resolve services that are scoped to the lifetime of the component.

    使用或()插入元件的 DI 服務 @inject InjectAttribute 不會 [Inject] 在元件的範圍內建立。DI services injected into the component using @inject or the InjectAttribute ([Inject]) aren't created in the component's scope. 若要使用元件的範圍,必須使用或來解析 ScopedServices.GetRequiredService 服務 ScopedServices.GetServiceTo use the component's scope, services must be resolved using ScopedServices.GetRequiredService or ScopedServices.GetService. 使用此提供者解析的任何服務 ScopedServices 都會從該相同範圍提供其相依性。Any services resolved using the ScopedServices provider have their dependencies provided from that same scope.

    @page "/preferences"
    @using Microsoft.Extensions.DependencyInjection
    @inherits OwningComponentBase
    
    <h1>User (@UserService.Name)</h1>
    
    <ul>
        @foreach (var setting in SettingService.GetSettings())
        {
            <li>@setting.SettingName: @setting.SettingValue</li>
        }
    </ul>
    
    @code {
        private IUserService UserService { get; set; }
        private ISettingService SettingService { get; set; }
    
        protected override void OnInitialized()
        {
            UserService = ScopedServices.GetRequiredService<IUserService>();
            SettingService = ScopedServices.GetRequiredService<ISettingService>();
        }
    }
    
  • OwningComponentBase<T>衍生自 OwningComponentBase ,並加入屬性 ServiceT 從範圍 DI 提供者傳回的實例。OwningComponentBase<T> derives from OwningComponentBase and adds a property Service that returns an instance of T from the scoped DI provider. IServiceProvider當應用程式需要使用元件範圍的 DI 容器中的一個主要服務時,此類型可方便您存取已設定範圍的服務,而不需使用的實例。This type is a convenient way to access scoped services without using an instance of IServiceProvider when there's one primary service the app requires from the DI container using the component's scope. ScopedServices屬性可供使用,因此應用程式可以取得其他類型的服務(如有需要)。The ScopedServices property is available, so the app can get services of other types, if necessary.

    @page "/users"
    @attribute [Authorize]
    @inherits OwningComponentBase<AppDbContext>
    
    <h1>Users (@Service.Users.Count())</h1>
    
    <ul>
        @foreach (var user in Service.Users)
        {
            <li>@user.UserName</li>
        }
    </ul>
    

從 DI 使用 Entity Framework DbCoNtextUse of Entity Framework DbContext from DI

從 web 應用程式中的 DI 取出的一個常見服務類型是 Entity Framework (EF) DbContext 物件。One common service type to retrieve from DI in web apps is Entity Framework (EF) DbContext objects. 使用註冊 EF 服務 IServiceCollection.AddDbContext 時,預設會將新增 DbContext 為範圍服務。Registering EF services using IServiceCollection.AddDbContext adds the DbContext as a scoped service by default. 將註冊為有範圍的服務可能會導致 Blazor 應用程式發生問題,因為它會導致 DbContext 實例長期存留,並在應用程式之間共用。Registering as a scoped service can lead to problems in Blazor apps because it causes DbContext instances to be long-lived and shared across the app. DbContext不是安全線程,而且不得同時使用。DbContext isn't thread-safe and must not be used concurrently.

根據應用程式而定,使用 OwningComponentBase 將的範圍限制 DbContext 為單一元件,可能會解決此問題。Depending on the app, using OwningComponentBase to limit the scope of a DbContext to a single component may solve the issue. 如果元件未平行使用,則從衍生 DbContext 元件, OwningComponentBase 並從中抓取, DbContext ScopedServices 就足以確保:If a component doesn't use a DbContext in parallel, deriving the component from OwningComponentBase and retrieving the DbContext from ScopedServices is sufficient because it ensures that:

  • 個別元件不會共用 DbContextSeparate components don't share a DbContext.
  • 存留的 DbContext 時間只會取決於元件。The DbContext lives only as long as the component depending on it.

如果單一元件可能會同時使用 DbContext (例如,每次使用者選取按鈕時),即使使用 OwningComponentBase 並不會避免並行 EF 作業的問題。If a single component might use a DbContext concurrently (for example, every time a user selects a button), even using OwningComponentBase doesn't avoid issues with concurrent EF operations. 在此情況下,請 DbContext 針對每個邏輯 EF 作業使用不同的。In that case, use a different DbContext for each logical EF operation. 請使用下列其中一種方法:Use either of the following approaches:

  • 建立 DbContext 直接使用 DbContextOptions<TContext> 做為引數的,您可以從 DI 抓取它,而且它是安全線程。Create the DbContext directly using DbContextOptions<TContext> as an argument, which can be retrieved from DI and is thread safe.

    @page "/example"
    @inject DbContextOptions<AppDbContext> DbContextOptions
    
    <ul>
        @foreach (var item in data)
        {
            <li>@item</li>
        }
    </ul>
    
    <button @onclick="LoadData">Load Data</button>
    
    @code {
        private List<string> data = new List<string>();
    
        private async Task LoadData()
        {
            data = await GetAsync();
            StateHasChanged();
        }
    
        public async Task<List<string>> GetAsync()
        {
            using (var context = new AppDbContext(DbContextOptions))
            {
                return await context.Products.Select(p => p.Name).ToListAsync();
            }
        }
    }
    
  • DbContext在服務容器中,以暫時性存留期註冊:Register the DbContext in the service container with a transient lifetime:

    • 註冊內容時,請使用 ServiceLifetime.TransientWhen registering the context, use ServiceLifetime.Transient. AddDbContext擴充方法會接受兩個類型的選擇性參數 ServiceLifetimeThe AddDbContext extension method takes two optional parameters of type ServiceLifetime. 若要使用此方法,只有 contextLifetime 參數需要是 ServiceLifetime.TransientTo use this approach, only the contextLifetime parameter needs to be ServiceLifetime.Transient. optionsLifetime可以保留其預設值 ServiceLifetime.ScopedoptionsLifetime can keep its default value of ServiceLifetime.Scoped.

      services.AddDbContext<AppDbContext>(options =>
           options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),
           ServiceLifetime.Transient);
      
    • 暫時性 DbContext 可以像平常一樣(使用)插入 @inject 至不會平行執行多個 EF 作業的元件。The transient DbContext can be injected as normal (using @inject) into components that will not execute multiple EF operations in parallel. 可能同時執行多個 EF 作業的人員可以 DbContext 使用,針對每個平行作業要求個別的物件 IServiceProvider.GetRequiredServiceThose that may perform multiple EF operations simultaneously can request separate DbContext objects for each parallel operation using IServiceProvider.GetRequiredService.

      @page "/example"
      @using Microsoft.Extensions.DependencyInjection
      @inject IServiceProvider ServiceProvider
      
      <ul>
          @foreach (var item in data)
          {
              <li>@item</li>
          }
      </ul>
      
      <button @onclick="LoadData">Load Data</button>
      
      @code {
          private List<string> data = new List<string>();
      
          private async Task LoadData()
          {
              data = await GetAsync();
              StateHasChanged();
          }
      
          public async Task<List<string>> GetAsync()
          {
              using (var context = ServiceProvider.GetRequiredService<AppDbContext>())
              {
                  return await context.Products.Select(p => p.Name).ToListAsync();
              }
          }
      }
      

其他資源Additional resources