ASP.NET Core Blazor z programem Entity Framework Core (EF Core)
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
W tym artykule wyjaśniono, jak używać programu Entity Framework Core (EF Core) w aplikacjach po stronie Blazor serwera.
Po stronie Blazor serwera jest stanowa struktura aplikacji. Aplikacja utrzymuje bieżące połączenie z serwerem, a stan użytkownika jest przechowywany w pamięci serwera w obwodzie. Przykładem stanu użytkownika są dane przechowywane w wystąpieniach usługi wstrzykiwania zależności (DI), które są ograniczone do obwodu. Unikatowy model aplikacji, który udostępnia, Blazor wymaga specjalnego podejścia do korzystania z platformy Entity Framework Core.
Uwaga
Ten artykuł dotyczy EF Core aplikacji po stronie Blazor serwera. Blazor WebAssembly aplikacje są uruchamiane w piaskownicy zestawu WebAssembly, która uniemożliwia korzystanie z większości bezpośrednich połączeń z bazą danych. Uruchamianie EF Core w programie Blazor WebAssembly wykracza poza zakres tego artykułu.
Te wskazówki dotyczą składników, które przyjmują interakcyjne renderowanie po stronie serwera (interakcyjne SSR) w systemie Blazor Web App.
Te wskazówki dotyczą Server
projektu hostowanego Blazor WebAssembly rozwiązania lub Blazor Server aplikacji.
Bezpieczny przepływ uwierzytelniania wymagany dla aplikacji produkcyjnych
W tym artykule jest używana lokalna baza danych, która nie wymaga uwierzytelniania użytkownika. Aplikacje produkcyjne powinny korzystać z najbezpieczniejszego dostępnego przepływu uwierzytelniania. Aby uzyskać więcej informacji na temat uwierzytelniania dla wdrożonych aplikacji testowych i produkcyjnychBlazor, zobacz artykuły w temacie BlazorZabezpieczenia i Identity węzeł.
W przypadku usług platformy Microsoft Azure zalecamy używanie tożsamości zarządzanych. Tożsamości zarządzane bezpiecznie uwierzytelniają się w usługach platformy Azure bez przechowywania poświadczeń w kodzie aplikacji. Aby uzyskać więcej informacji, zobacz następujące zasoby:
- Co to są tożsamości zarządzane dla zasobów platformy Azure? (Dokumentacja firmy Microsoft Entra)
- Dokumentacja usług platformy Azure
Przykładowa aplikacja
Przykładowa aplikacja została utworzona jako odwołanie do aplikacji po stronie Blazor serwera, które używają polecenia EF Core. Przykładowa aplikacja zawiera siatkę z operacjami sortowania i filtrowania, usuwania, dodawania i aktualizowania.
W przykładzie pokazano użycie funkcji do obsługi optymistycznej EF Core współbieżności. Jednak natywne tokeny współbieżności generowane przez bazę danych nie są obsługiwane w przypadku baz danych SQLite, czyli dostawcy bazy danych dla przykładowej aplikacji. Aby zademonstrować współbieżność z przykładową aplikacją, należy przyjąć innego dostawcę bazy danych, który obsługuje tokeny współbieżności generowane przez bazę danych (na przykład dostawcę programu SQL Server).
Wyświetl lub pobierz przykładowy kod (jak pobrać): wybierz folder zgodny z wdrażaną wersją platformy .NET. W folderze wersji uzyskaj dostęp do przykładu o nazwie BlazorWebAppEFCore
.
Wyświetl lub pobierz przykładowy kod (jak pobrać): wybierz folder zgodny z wdrażaną wersją platformy .NET. W folderze wersji uzyskaj dostęp do przykładu o nazwie BlazorServerEFCoreSample
.
W przykładzie użyto lokalnej bazy danych SQLite , aby można było jej używać na dowolnej platformie. Przykład umożliwia również skonfigurowanie rejestrowania bazy danych w celu wyświetlenia wygenerowanych zapytań SQL. Jest to skonfigurowane w programie appsettings.Development.json
:
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
Składniki siatki, dodawania i wyświetlania używają wzorca "kontekst na operację", w którym jest tworzony kontekst dla każdej operacji. Składnik edycji używa wzorca "context-per-component", w którym kontekst jest tworzony dla każdego składnika.
Uwaga
Niektóre przykłady kodu w tym temacie wymagają przestrzeni nazw i usług, które nie są wyświetlane. Aby sprawdzić w pełni działający kod, w tym wymagane @using
dyrektywy i @inject
przykłady Razor , zobacz przykładową aplikację.
Samouczek dotyczący tworzenia Blazor aplikacji bazy danych filmów
Aby zapoznać się z samouczkiem dotyczącym tworzenia aplikacji używanej EF Core do pracy z bazą danych, zobacz Tworzenie Blazor aplikacji bazy danych filmów (Omówienie). W samouczku pokazano, jak utworzyć element, który Blazor Web App może wyświetlać filmy w bazie danych filmów i zarządzać nimi.
Dostęp do bazy danych
EF Core opiera się na metodzie DbContext konfigurowania dostępu do bazy danych i działania jako jednostki pracy. EF CoreAddDbContext Udostępnia rozszerzenie dla aplikacji ASP.NET Core, które rejestrują kontekst jako usługę o określonym zakresie. W aplikacjach po stronie Blazor serwera rejestracje usług w zakresie mogą być problematyczne, ponieważ wystąpienie jest współużytkowane między składnikami w obwodzie użytkownika. DbContext nie jest bezpieczny wątkiem i nie jest przeznaczony do współbieżnego użycia. Istniejące okresy istnienia są nieodpowiednie z następujących powodów:
- Stan pojedynczego udziału dla wszystkich użytkowników aplikacji i prowadzi do niewłaściwego współbieżnego użycia.
- Zakres (wartość domyślna) stanowi podobny problem między składnikami dla tego samego użytkownika.
- Przejściowe wyniki w nowym wystąpieniu na żądanie, ale ponieważ składniki mogą być długotrwałe, powoduje to dłuższy kontekst niż może być zamierzony.
Poniższe zalecenia zostały zaprojektowane w celu zapewnienia spójnego podejścia do korzystania z EF Core aplikacji po stronie Blazor serwera.
Rozważ użycie jednego kontekstu na operację. Kontekst jest przeznaczony do szybkiego tworzenia wystąpienia niskiego obciążenia:
using var context = new MyContext(); return await context.MyEntities.ToListAsync();
Użyj flagi, aby zapobiec wielu równoczesnym operacjom:
if (Loading) { return; } try { Loading = true; ... } finally { Loading = false; }
Umieść operacje po
Loading = true;
wierszu wtry
bloku.Bezpieczeństwo wątków nie jest problemem, więc logika ładowania nie wymaga blokowania rekordów bazy danych. Logika ładowania służy do wyłączania kontrolek interfejsu użytkownika, dzięki czemu użytkownicy nie mogą przypadkowo wybierać przycisków ani aktualizować pól podczas pobierania danych.
Jeśli istnieje prawdopodobieństwo, że wiele wątków może uzyskać dostęp do tego samego bloku kodu, wstrzyknąć fabrykę i utworzyć nowe wystąpienie na operację. W przeciwnym razie wstrzykiwanie i używanie kontekstu jest zwykle wystarczające.
W przypadku długotrwałych operacji, które korzystają ze EF Coreśledzenia zmian lub kontroli współbieżności, określ zakres kontekstu na okres istnienia składnika.
Nowe DbContext
wystąpienia
Najszybszym sposobem utworzenia nowego DbContext wystąpienia jest utworzenie new
nowego wystąpienia. Istnieją jednak scenariusze wymagające rozwiązania dodatkowych zależności:
- Użyj polecenia
DbContextOptions
, aby skonfigurować kontekst. - Użycie parametry połączenia na DbContext, na przykład w przypadku korzystania z modelu ASP.NET CoreIdentity. Aby uzyskać więcej informacji, zobacz Multi-tenancy (EF Core dokumentacja).
Ostrzeżenie
Nie przechowuj wpisów tajnych aplikacji, parametry połączenia, poświadczeń, haseł, osobistych numerów identyfikacyjnych (PIN), prywatnego kodu C#/.NET lub kluczy prywatnych/tokenów w kodzie po stronie klienta, który jest zawsze niepewny. W środowiskach testowych/przejściowych i produkcyjnych kod po stronie Blazor serwera i internetowe interfejsy API powinny używać bezpiecznych przepływów uwierzytelniania, które unikają utrzymywania poświadczeń w kodzie projektu lub plikach konfiguracji. Poza lokalnymi testami programistycznymi zalecamy unikanie używania zmiennych środowiskowych do przechowywania poufnych danych, ponieważ zmienne środowiskowe nie są najbezpieczniejszym podejściem. W przypadku lokalnego testowania programistycznego narzędzie Secret Manager jest zalecane do zabezpieczania poufnych danych. Aby uzyskać więcej informacji, zobacz Bezpieczne utrzymywanie poufnych danych i poświadczeń.
Zalecanym podejściem do utworzenia nowego DbContext elementu z zależnościami jest użycie fabryki. EF Core Wersja 5.0 lub nowsza udostępnia wbudowaną fabrykę do tworzenia nowych kontekstów.
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace BlazorServerDbContextExample.Data
{
public class DbContextFactory<TContext>
: IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public DbContextFactory(IServiceProvider provider)
{
this.provider = provider ?? throw new ArgumentNullException(
$"{nameof(provider)}: You must configure an instance of " +
"IServiceProvider");
}
public TContext CreateDbContext() =>
ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
W poprzedniej fabryce:
- ActivatorUtilities.CreateInstance spełnia wszelkie zależności za pośrednictwem dostawcy usług.
- IDbContextFactory<TContext> jest dostępny w EF Core wersji ASP.NET Core 5.0 lub nowszej, dlatego interfejs jest implementowany w przykładowej aplikacji dla platformy ASP.NET Core 3.x.
W poniższym przykładzie skonfigurowaliśmy bibliotekę SQLite i włączono rejestrowanie danych. Kod używa metody rozszerzenia (AddDbContextFactory
), aby skonfigurować fabrykę baz danych dla di i udostępnić opcje domyślne:
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
Fabryka jest wstrzykiwana do składników i używana do tworzenia nowych DbContext
wystąpień.
Na home stronie przykładowej aplikacji IDbContextFactory<ContactContext>
jest wstrzykiwany do składnika:
@inject IDbContextFactory<ContactContext> DbFactory
Obiekt DbContext
jest tworzony przy użyciu fabryki (DbFactory
) w celu usunięcia kontaktu w metodzie DeleteContactAsync
:
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
if (Wrapper is not null && context.Contacts is not null)
{
var contact = await context.Contacts
.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);
if (contact is not null)
{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}
Filters.Loading = false;
await ReloadAsync();
}
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
if (Wrapper is not null && context.Contacts is not null)
{
var contact = await context.Contacts
.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);
if (contact is not null)
{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}
Filters.Loading = false;
await ReloadAsync();
}
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
if (Wrapper is not null && context.Contacts is not null)
{
var contact = await context.Contacts
.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);
if (contact is not null)
{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}
Filters.Loading = false;
await ReloadAsync();
}
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
if (Wrapper is not null && context.Contacts is not null)
{
var contact = await context.Contacts
.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);
if (contact is not null)
{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}
Filters.Loading = false;
await ReloadAsync();
}
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
var contact = await context.Contacts.FirstAsync(
c => c.Id == Wrapper.DeleteRequestId);
if (contact != null)
{
context.Contacts.Remove(contact);
await context.SaveChangesAsync();
}
Filters.Loading = false;
await ReloadAsync();
}
private async Task DeleteContactAsync()
{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;
var contact = await context.Contacts.FirstAsync(
c => c.Id == Wrapper.DeleteRequestId);
if (contact != null)
{
context.Contacts.Remove(contact);
await context.SaveChangesAsync();
}
Filters.Loading = false;
await ReloadAsync();
}
Uwaga
Filters
jest wstrzykniętym IContactFilters
elementem i Wrapper
jest odwołaniem do GridWrapper
składnika. Home
Zobacz składnik (Components/Pages/Home.razor
) w przykładowej aplikacji.
Uwaga
Filters
jest wstrzykniętym IContactFilters
elementem i Wrapper
jest odwołaniem do GridWrapper
składnika. Index
Zobacz składnik (Pages/Index.razor
) w przykładowej aplikacji.
Zakres do okresu istnienia składnika
Możesz utworzyć element DbContext , który istnieje przez okres istnienia składnika. Dzięki temu można używać go jako jednostki pracy i korzystać z wbudowanych funkcji, takich jak śledzenie zmian i rozpoznawanie współbieżności.
Za pomocą fabryki można utworzyć kontekst i śledzić go przez cały okres istnienia składnika. Najpierw zaimplementuj IDisposable i wstrzyknąć fabrykę, jak pokazano w składniku EditContact
(Components/Pages/EditContact.razor
):
Za pomocą fabryki można utworzyć kontekst i śledzić go przez cały okres istnienia składnika. Najpierw zaimplementuj IDisposable i wstrzyknąć fabrykę, jak pokazano w składniku EditContact
(Pages/EditContact.razor
):
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
Przykładowa aplikacja gwarantuje, że kontekst zostanie usunięty po usunięciu składnika:
public void Dispose() => Context?.Dispose();
public void Dispose() => Context?.Dispose();
public void Dispose()
{
Context?.Dispose();
}
public void Dispose()
{
Context?.Dispose();
}
public void Dispose()
{
Context?.Dispose();
}
public void Dispose()
{
Context?.Dispose();
}
Na koniec zostanie zastąpiony, OnInitializedAsync
aby utworzyć nowy kontekst. W przykładowej aplikacji OnInitializedAsync
ładuje kontakt w tej samej metodzie:
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
if (Context is not null && Context.Contacts is not null)
{
var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);
if (contact is not null)
{
Contact = contact;
}
}
}
finally
{
Busy = false;
}
}
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
if (Context is not null && Context.Contacts is not null)
{
var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);
if (contact is not null)
{
Contact = contact;
}
}
}
finally
{
Busy = false;
}
}
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
if (Context is not null && Context.Contacts is not null)
{
var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);
if (contact is not null)
{
Contact = contact;
}
}
}
finally
{
Busy = false;
}
await base.OnInitializedAsync();
}
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
if (Context is not null && Context.Contacts is not null)
{
var contact = await Context.Contacts.SingleOrDefaultAsync(c => c.Id == ContactId);
if (contact is not null)
{
Contact = contact;
}
}
}
finally
{
Busy = false;
}
await base.OnInitializedAsync();
}
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
Contact = await Context.Contacts
.SingleOrDefaultAsync(c => c.Id == ContactId);
}
finally
{
Busy = false;
}
await base.OnInitializedAsync();
}
protected override async Task OnInitializedAsync()
{
Busy = true;
try
{
Context = DbFactory.CreateDbContext();
Contact = await Context.Contacts
.SingleOrDefaultAsync(c => c.Id == ContactId);
}
finally
{
Busy = false;
}
await base.OnInitializedAsync();
}
W powyższym przykładzie:
- Gdy
Busy
jest ustawiona wartośćtrue
, mogą rozpocząć się operacje asynchroniczne. PoBusy
ustawieniu elementu z powrotem nafalse
operacje asynchroniczne powinny zostać zakończone. - Umieść dodatkową logikę
catch
obsługi błędów w bloku.
Włączanie rejestrowania poufnych danych
EnableSensitiveDataLogging zawiera dane aplikacji w komunikatach o wyjątkach i rejestrowaniu struktury. Zarejestrowane dane mogą zawierać wartości przypisane do właściwości wystąpień jednostki i wartości parametrów dla poleceń wysyłanych do bazy danych. Rejestrowanie danych EnableSensitiveDataLogging za pomocą elementu jest zagrożeniem bezpieczeństwa, ponieważ może uwidaczniać hasła i inne dane osobowe podczas rejestrowania instrukcji SQL wykonywanych względem bazy danych.
Zalecamy włączenie EnableSensitiveDataLogging tylko na potrzeby programowania i testowania:
#if DEBUG
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
#else
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));
#endif