Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .openpublishing.redirection.json
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,11 @@
"source_path": "aspnetcore/blazor/tutorials/signalr-blazor-preview.md",
"redirect_url": "/aspnet/core/blazor/tutorials/signalr-blazor",
"redirect_document_id": false
},
{
"source_path": "aspnetcore/blazor/blazor-server-ef-core.md",
"redirect_url": "/aspnet/core/blazor/blazor-ef-core",
"redirect_document_id": false
}
]
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
---
title: ASP.NET Core Blazor Server with Entity Framework Core (EF Core)
title: ASP.NET Core Blazor with Entity Framework Core (EF Core)
author: guardrex
description: Learn how to use Entity Framework Core (EF Core) in Blazor Server apps.
description: Learn how to use Entity Framework Core (EF Core) in Blazor apps.
monikerRange: '>= aspnetcore-3.1'
ms.author: jeliknes
ms.custom: mvc
ms.date: 03/27/2023
uid: blazor/blazor-server-ef-core
uid: blazor/blazor-ef-core
---
# ASP.NET Core Blazor Server with Entity Framework Core (EF Core)
# ASP.NET Core Blazor with Entity Framework Core (EF Core)

[!INCLUDE[](~/includes/not-latest-version.md)]

This article explains how to use [Entity Framework Core (EF Core)](/ef/core/) in Blazor Server apps.
This article explains how to use [Entity Framework Core (EF Core)](/ef/core/) in server-side Blazor apps.

Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a *circuit*. One example of user state is data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core.
Server-side Blazor is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a *circuit*. One example of user state is data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. The unique application model that Blazor provides requires a special approach to use Entity Framework Core.

> [!NOTE]
> This article addresses EF Core in Blazor Server apps. Blazor WebAssembly apps run in a WebAssembly sandbox that prevents most direct database connections. Running EF Core in Blazor WebAssembly is beyond the scope of this article.
> This article addresses EF Core in server-side Blazor apps. Blazor WebAssembly apps run in a WebAssembly sandbox that prevents most direct database connections. Running EF Core in Blazor WebAssembly is beyond the scope of this article.

## Sample app

The sample app was built as a reference for Blazor Server apps that use EF Core. The sample app includes a grid with sorting and filtering, delete, add, and update operations. The sample demonstrates use of EF Core to handle optimistic concurrency.
The sample app was built as a reference for server-side Blazor apps that use EF Core. The sample app includes a grid with sorting and filtering, delete, add, and update operations. The sample demonstrates use of EF Core to handle optimistic concurrency.

[View or download sample code](https://github.com/dotnet/blazor-samples) ([how to download](xref:index#how-to-download-a-sample))

The sample uses a local [SQLite](https://www.sqlite.org/index.html) database so that it can be used on any platform. The sample also configures database logging to show the SQL queries that are generated. This is configured in `appsettings.Development.json`:

:::moniker range=">= aspnetcore-7.0"
:::moniker range=">= aspnetcore-8.0"

:::code language="json" source="~/../blazor-samples/8.0/BlazorWebAppEFCore/appsettings.Development.json" highlight="8":::

:::moniker-end

:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"

:::code language="json" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/appsettings.Development.json" highlight="8":::

Expand Down Expand Up @@ -58,13 +64,13 @@ The grid, add, and view components use the "context-per-operation" pattern, wher

## Database access

EF Core relies on a <xref:Microsoft.EntityFrameworkCore.DbContext> as the means to [configure database access](/ef/core/miscellaneous/configuring-dbcontext) and act as a [*unit of work*](https://martinfowler.com/eaaCatalog/unitOfWork.html). EF Core provides the <xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContext%2A> extension for ASP.NET Core apps that registers the context as a *scoped* service by default. In Blazor Server apps, scoped service registrations can be problematic because the instance is shared across components within the user's circuit. <xref:Microsoft.EntityFrameworkCore.DbContext> isn't thread safe and isn't designed for concurrent use. The existing lifetimes are inappropriate for these reasons:
EF Core relies on a <xref:Microsoft.EntityFrameworkCore.DbContext> as the means to [configure database access](/ef/core/miscellaneous/configuring-dbcontext) and act as a [*unit of work*](https://martinfowler.com/eaaCatalog/unitOfWork.html). EF Core provides the <xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContext%2A> extension for ASP.NET Core apps that registers the context as a *scoped* service by default. In server-side Blazor apps, scoped service registrations can be problematic because the instance is shared across components within the user's circuit. <xref:Microsoft.EntityFrameworkCore.DbContext> isn't thread safe and isn't designed for concurrent use. The existing lifetimes are inappropriate for these reasons:

* **Singleton** shares state across all users of the app and leads to inappropriate concurrent use.
* **Scoped** (the default) poses a similar issue between components for the same user.
* **Transient** results in a new instance per request; but as components can be long-lived, this results in a longer-lived context than may be intended.

The following recommendations are designed to provide a consistent approach to using EF Core in Blazor Server apps.
The following recommendations are designed to provide a consistent approach to using EF Core in server-side Blazor apps.

* By default, consider using one context per operation. The context is designed for fast, low overhead instantiation:

Expand Down Expand Up @@ -124,7 +130,13 @@ In the preceding factory:

The following example configures [SQLite](https://www.sqlite.org/index.html) and enables data logging. The code uses an extension method (`AddDbContextFactory`) to configure the database factory for DI and provide default options:

:::moniker range=">= aspnetcore-7.0"
:::moniker range=">= aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/8.0/BlazorWebAppEFCore/Program.cs" id="snippet1":::

:::moniker-end

:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Program.cs" id="snippet1":::

Expand All @@ -150,15 +162,21 @@ The following example configures [SQLite](https://www.sqlite.org/index.html) and

The factory is injected into components and used to create new `DbContext` instances.

In `Pages/Index.razor` of the [sample app](https://github.com/dotnet/blazor-samples/blob/main/7.0/BlazorServerEFCoreSample/Pages/Index.razor), `IDbContextFactory<ContactContext>` is injected into the component:
In the home page of the sample app, `IDbContextFactory<ContactContext>` is injected into the component:

```razor
@inject IDbContextFactory<ContactContext> DbFactory
```

A `DbContext` is created using the factory (`DbFactory`) to delete a contact in the `DeleteContactAsync` method:

:::moniker range=">= aspnetcore-7.0"
:::moniker range=">= aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/8.0/BlazorWebAppEFCore/Components/Pages/Home.razor" id="snippet1":::

:::moniker-end

:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Pages/Index.razor" id="snippet1":::

Expand All @@ -182,13 +200,35 @@ A `DbContext` is created using the factory (`DbFactory`) to delete a contact in

:::moniker-end

:::moniker range=">= aspnetcore-8.0"

> [!NOTE]
> `Filters` is an injected `IContactFilters`, and `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Home` component (`Components/Pages/Home.razor`) in the sample app.

:::moniker-end

:::moniker range="< aspnetcore-8.0"

> [!NOTE]
> `Filters` is an injected `IContactFilters`, and `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Index` component (`Pages/Index.razor`) in the [sample app](https://github.com/dotnet/blazor-samples/blob/main/6.0/BlazorServerEFCoreSample/Pages/Index.razor).
> `Filters` is an injected `IContactFilters`, and `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Index` component (`Pages/Index.razor`) in the sample app.

:::moniker-end

## Scope to the component lifetime

You may wish to create a <xref:Microsoft.EntityFrameworkCore.DbContext> that exists for the lifetime of a component. This allows you to use it as a [unit of work](https://martinfowler.com/eaaCatalog/unitOfWork.html) and take advantage of built-in features, such as change tracking and concurrency resolution.
You can use the factory to create a context and track it for the lifetime of the component. First, implement <xref:System.IDisposable> and inject the factory as shown in `Pages/EditContact.razor`:

:::moniker range=">= aspnetcore-8.0"

You can use the factory to create a context and track it for the lifetime of the component. First, implement <xref:System.IDisposable> and inject the factory as shown in the `EditContact` component (`Components/Pages/EditContact.razor`):

:::moniker-end

:::moniker range="< aspnetcore-8.0"

You can use the factory to create a context and track it for the lifetime of the component. First, implement <xref:System.IDisposable> and inject the factory as shown in the `EditContact` component (`Pages/EditContact.razor`):

:::moniker-end

```razor
@implements IDisposable
Expand All @@ -197,7 +237,13 @@ You can use the factory to create a context and track it for the lifetime of the

The sample app ensures the context is disposed when the component is disposed:

:::moniker range=">= aspnetcore-7.0"
:::moniker range=">= aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/8.0/BlazorWebAppEFCore/Components/Pages/EditContact.razor" id="snippet1":::

:::moniker-end

:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet1":::

Expand All @@ -223,7 +269,13 @@ The sample app ensures the context is disposed when the component is disposed:

Finally, [`OnInitializedAsync`](xref:blazor/components/lifecycle) is overridden to create a new context. In the sample app, [`OnInitializedAsync`](xref:blazor/components/lifecycle) loads the contact in the same method:

:::moniker range=">= aspnetcore-7.0"
:::moniker range=">= aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/8.0/BlazorWebAppEFCore/Components/Pages/EditContact.razor" id="snippet2":::

:::moniker-end

:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"

:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet2":::

Expand Down
4 changes: 1 addition & 3 deletions aspnetcore/blazor/fundamentals/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,7 @@ In spite of the scoped service registration in the `Program` file and the longev

## Use of an Entity Framework Core (EF Core) DbContext from DI

<!-- UPDATE 8.0 The UID will change -->

For more information, see <xref:blazor/blazor-server-ef-core>.
For more information, see <xref:blazor/blazor-ef-core>.

## Detect client-side transient disposables

Expand Down
4 changes: 2 additions & 2 deletions aspnetcore/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -583,8 +583,8 @@ items:
uid: blazor/host-and-deploy/webassembly-deployment-layout
- name: Multiple hosted WebAssembly apps
uid: blazor/host-and-deploy/multiple-hosted-webassembly
- name: Blazor Server and EF Core
uid: blazor/blazor-server-ef-core
- name: Blazor with EF Core
uid: blazor/blazor-ef-core
- name: Advanced scenarios
uid: blazor/advanced-scenarios
- name: Client-side development
Expand Down
2 changes: 1 addition & 1 deletion aspnetcore/whats-new/dotnet-AspNetCore.Docs-mod5.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Welcome to what's new in the ASP.NET Core docs for May 2023. This article lists
- Components overview versioning
- <xref:blazor/host-and-deploy/index> - Expand lead-in coverage on app base path
- <xref:blazor/fundamentals/signalr> - Blazor SignalR fundamentals article versioning
- <xref:blazor/blazor-server-ef-core> - Blazor Server EF Core article versioning
- <xref:blazor/blazor-ef-core> - Blazor Server EF Core article versioning
- <xref:blazor/components/templated-components> - Blazor article versioning
- <xref:blazor/tutorials/build-a-blazor-app> - Blazor article versioning
- <xref:blazor/host-and-deploy/multiple-hosted-webassembly> - Subpath scenario for multiple hosted WASM apps
Expand Down