ASP.NET Core Blazor s Entity Framework Core (EF Core)
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Tento článek vysvětluje, jak používat Entity Framework Core (EF Core) v serverových Blazor aplikacích.
Na straně Blazor serveru je stavová aplikační architektura. Aplikace udržuje trvalé připojení k serveru a stav uživatele se uchovává v paměti serveru v okruhu. Jedním z příkladů stavu uživatele jsou data uložená v instancích služby injektáže závislostí (DI), které jsou vymezeny na okruh. Jedinečný aplikační model, který Blazor poskytuje, vyžaduje speciální přístup k používání Entity Framework Core.
Poznámka:
Tento článek se zabývá EF Core aplikacemi na straně Blazor serveru. Blazor WebAssembly aplikace běží v sandboxu WebAssembly, který brání většině přímých databázových připojení. Blazor WebAssembly Spuštění EF Core je nad rámec tohoto článku.
Tyto pokyny platí pro komponenty, které přijímají interaktivní vykreslování na straně serveru (interaktivní SSR) ve Blazor webové aplikaci.
Tyto pokyny platí pro Server
projekt hostovaného Blazor WebAssembly řešení nebo Blazor Server aplikace.
Ukázková aplikace
Ukázková aplikace byla vytvořena jako referenční informace pro aplikace na straně Blazor serveru, které používají EF Core. Ukázková aplikace obsahuje mřížku s operacemi řazení a filtrování, odstraňování, přidávání a aktualizací. Ukázka ukazuje použití EF Core ke zpracování optimistické souběžnosti.
Zobrazení nebo stažení vzorového kódu (postup stažení): Vyberte složku, která odpovídá verzi .NET, kterou přijímáte. Ve složce verze přejděte k ukázce s názvem BlazorWebAppEFCore
.
Zobrazení nebo stažení vzorového kódu (postup stažení): Vyberte složku, která odpovídá verzi .NET, kterou přijímáte. Ve složce verze přejděte k ukázce s názvem BlazorServerEFCoreSample
.
Ukázka používá místní databázi SQLite , aby ji bylo možné použít na libovolné platformě. Ukázka také nakonfiguruje protokolování databáze tak, aby zobrazovala dotazy SQL, které se generují. Toto nastavení je nakonfigurováno v 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": "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"
}
}
}
Součástí mřížky, přidání a zobrazení se používá vzor "kontext podle operace", kde se pro každou operaci vytvoří kontext. Komponenta pro úpravy používá vzor context-per-component, kde se pro každou komponentu vytvoří kontext.
Poznámka:
Některé příklady kódu v tomto tématu vyžadují obory názvů a služby, které se nezobrazují. Pokud chcete zkontrolovat plně funkční kód, včetně požadovaných @using
a @inject
direktiv pro Razor příklady, podívejte se na ukázkovou aplikaci.
Přístup k databázi
EF Corespoléhá na způsob DbContext konfigurace přístupu k databázi a funguje jako jednotka práce. EF CoreAddDbContext poskytuje rozšíření pro aplikace ASP.NET Core, které ve výchozím nastavení registrují kontext jako službu s vymezeným oborem. V aplikacích na straně Blazor serveru můžou být registrace vymezených služeb problematické, protože instance se sdílí mezi komponentami v okruhu uživatele. DbContext není bezpečné vlákno a není navržené pro souběžné použití. Stávající životnosti jsou nevhodné z těchto důvodů:
- Singleton sdílí stav napříč všemi uživateli aplikace a vede k nevhodnému souběžnému použití.
- Obor (výchozí) představuje podobný problém mezi komponentami pro stejného uživatele.
- Přechodné výsledky v nové instanci na požadavek, ale vzhledem k tomu, že komponenty mohou být dlouhodobé, výsledkem je delší kontext, než může být zamýšleno.
Následující doporučení jsou navržená tak, aby poskytovala konzistentní přístup k používání EF Core v aplikacích na straně Blazor serveru.
Ve výchozím nastavení zvažte použití jednoho kontextu pro každou operaci. Kontext je navržený pro rychlé vytváření instancí s nízkou režií:
using var context = new MyContext(); return await context.MyEntities.ToListAsync();
Pokud chcete zabránit více souběžným operacím, použijte příznak:
if (Loading) { return; } try { Loading = true; ... } finally { Loading = false; }
Umístěte operace za
Loading = true;
řádek vtry
bloku.Logika načítání nevyžaduje uzamčení záznamů databáze, protože zabezpečení vláken není problém. Logika načítání se používá k zakázání ovládacích prvků uživatelského rozhraní, aby uživatelé neúmyslně nevybírejli tlačítka nebo aktualizovali pole při načítání dat.
Pokud existuje nějaká šance, že více vláken může přistupovat ke stejnému bloku kódu, vkněte továrnu a vytvořte novou instanci na operaci. V opačném případě je vkládání a používání kontextu obvykle dostatečné.
U delších EF Coreoperací, které využívají sledování změn nebo řízení souběžnosti, rozsah kontextu na dobu životnosti komponenty.
Nové DbContext
instance
Nejrychlejší způsob, jak vytvořit novou DbContext instanci, je použití new
k vytvoření nové instance. Existují však scénáře, které vyžadují překlad dalších závislostí:
- Použití
DbContextOptions
ke konfiguraci kontextu. - Použití připojovací řetězec podle DbContext, například při použití modelu ASP.NET CoreIdentity. Další informace najdete v tématu Víceklientská architektura (EF Core dokumentace).
Doporučeným přístupem k vytvoření nové DbContext se závislostmi je použití továrny. EF Core 5.0 nebo novější poskytuje integrovanou továrnu pro vytváření nových kontextů.
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);
}
}
V předchozí továrně:
- ActivatorUtilities.CreateInstance splňuje všechny závislosti prostřednictvím poskytovatele služeb.
IDbContextFactory
je k dispozici v EF Core ASP.NET Core 5.0 nebo novějším, takže rozhraní se implementuje v ukázkové aplikaci pro ASP.NET Core 3.x.
Následující příklad nakonfiguruje SQLite a povolí protokolování dat. Kód používá metodu rozšíření (AddDbContextFactory
) ke konfiguraci databázové továrny pro DI a poskytnutí výchozích možností:
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"));
Továrna se vloží do komponent a použije se k vytváření nových DbContext
instancí.
Na domovské stránce ukázkové aplikace IDbContextFactory<ContactContext>
se vloží do komponenty:
@inject IDbContextFactory<ContactContext> DbFactory
Vytvoří DbContext
se pomocí továrny (DbFactory
) k odstranění kontaktu v DeleteContactAsync
metodě:
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();
}
Poznámka:
Filters
je vložený IContactFilters
a Wrapper
je součástí odkaz na komponentu GridWrapper
. Podívejte se na komponentu Home
(Components/Pages/Home.razor
) v ukázkové aplikaci.
Poznámka:
Filters
je vložený IContactFilters
a Wrapper
je součástí odkaz na komponentu GridWrapper
. Podívejte se na komponentu Index
(Pages/Index.razor
) v ukázkové aplikaci.
Rozsah životnosti komponenty
Možná budete chtít vytvořit DbContext , která existuje po celou dobu životnosti komponenty. Můžete ho používat jako jednotku práce a využívat integrované funkce, jako je sledování změn a řešení souběžnosti.
Pomocí továrny můžete vytvořit kontext a sledovat ho po celou dobu životnosti komponenty. Nejprve implementujte IDisposable a vkládat továrnu, jak je znázorněno v komponentě EditContact
(Components/Pages/EditContact.razor
):
Pomocí továrny můžete vytvořit kontext a sledovat ho po celou dobu životnosti komponenty. Nejprve implementujte IDisposable a vkládat továrnu, jak je znázorněno v komponentě EditContact
(Pages/EditContact.razor
):
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
Ukázková aplikace zajistí, že se kontext odstraní, když je komponenta uvolněná:
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();
}
Nakonec se přepíše, OnInitializedAsync
aby se vytvořil nový kontext. V ukázkové aplikaci OnInitializedAsync
načte kontakt ve stejné metodě:
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();
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();
}
V předchozím příkladu:
- Pokud
Busy
je nastavena natrue
, asynchronní operace mohou začínat. PoBusy
nastavení zpět nafalse
asynchronní operace by měly být dokončeny. - Do bloku umístěte další logiku
catch
zpracování chyb.
Povolení protokolování citlivých dat
EnableSensitiveDataLogging zahrnuje data aplikací ve zprávách výjimek a protokolování architektury. Protokolovaná data můžou zahrnovat hodnoty přiřazené vlastnostem instancí entit a hodnot parametrů pro příkazy odeslané do databáze. Protokolování dat s bezpečnostními rizikyEnableSensitiveDataLogging, protože může vystavit hesla a další identifikovatelné osobní údaje (PII), když protokoluje příkazy SQL spuštěné v databázi.
Doporučujeme povolit EnableSensitiveDataLogging pouze vývoj a testování:
#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