ASP.NET Core Blazor med Entity Framework Core (EF Core)
Anmärkning
Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.
Viktig
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
Den aktuella utgåvan finns i .NET 9-versionen av den här artikeln .
Den här artikeln beskriver hur du använder Entity Framework Core (EF Core) i Blazor appar på serversidan.
Blazor på serversidan är ett tillståndskänsligt appramverk. Appen upprätthåller en pågående anslutning till servern och användarens tillstånd lagras i serverns minne i en krets. Ett exempel på användartillstånd är data som lagras i beroendeinmatning (DI) tjänstinstanser som är begränsade till kretsen. Den unika programmodell som Blazor tillhandahåller kräver en särskild metod för att använda Entity Framework Core.
Anteckning
Den här artikeln tar upp EF Core i Blazor appar på serversidan. Blazor WebAssembly appar körs i en WebAssembly-sandbox-miljö som förhindrar de flesta direkta databasanslutningar. Att köra EF Core i Blazor WebAssembly ligger utanför den här artikelns omfång.
Den här vägledningen gäller för komponenter som använder interaktiv återgivning på serversidan (interaktiv SSR) i en Blazor Web App.
Den här vägledningen gäller för Server
projekt för en värdbaserad Blazor WebAssembly lösning eller en Blazor Server app.
Säkert autentiseringsflöde som krävs för produktionsappar
Den här artikeln använder en lokal databas som inte kräver användarautentisering. Produktionsappar bör använda det säkraste tillgängliga autentiseringsflödet. Mer information om autentisering för distribuerade test- och produktionsappar Blazor finns i artiklarna i noden BlazorSäkerhet och Identity nod.
För Microsoft Azure-tjänster rekommenderar vi att du använder hanterade identiteter. Hanterade identiteter autentiserar säkert till Azure-tjänster utan att lagra autentiseringsuppgifter i appkod. Mer information finns i följande resurser:
- Vad är hanterade identiteter för Azure-resurser? (Microsoft Entra-dokumentation)
- Dokumentation om Azure-tjänster
Exempelapp
Exempelappen skapades som en referens för Blazor appar på serversidan som använder EF Core. Exempelappen innehåller ett rutnät med sorterings- och filtreringsåtgärder, borttagning, tillägg och uppdatering.
Visa eller ladda ned exempelkod (hur du laddar ned): Välj den mapp som matchar den version av .NET som du använder. I versionsmappen kommer du åt exemplet med namnet BlazorWebAppEFCore
.
Visa eller ladda ned exempelkod (hur du laddar ned): Välj den mapp som matchar den version av .NET som du använder. I versionsmappen kommer du åt exemplet med namnet BlazorServerEFCoreSample
.
Använda exemplet med SQLite
Exemplet använder en lokal SQLite- databas så att den kan användas på valfri plattform.
Exemplet visar användningen av EF Core för att hantera optimistisk samtidighet. Men inbyggda databasgenererade samtidighetstoken stöds inte för SQLite-databaser, som är databasprovidern för exempelappen. Om du vill demonstrera samtidighet med exempelappen använder du en annan databasprovider som stöder databasgenererade samtidighetstoken (till exempel SQL Server-providern). Du kan använda SQL Server för exempelappen genom att följa anvisningarna i nästa avsnitt Använd exemplet med SQL Server och optimistisk samtidighet.
Använd exemplet med SQL Server och optimistisk samtidighet
Exemplet visar användning av EF Core för att hantera optimistisk samtidighet, men bara för en databasleverantör som använder databasgenererade inbyggda samtidighetstoken, vilket är en funktion som stöds för SQL Server. För att demonstrera samtidighet med exempelappen kan exempelappen konverteras från SQLite-providern för att använda SQL Server-providern med en ny SQL Server-databas som skapats med hjälp av .NET-byggnadsställningar.
Använd följande vägledning för att använda SQL Server för exempelappen med hjälp av Visual Studio.
Öppna filen Program
(Program.cs
) och kommentera ut raderna som lägger till databasens kontextfabrik med SQLite-providern:
- 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"));
Spara filen Program.cs
.
Högerklicka på projektet i Solution Explorer och välj Lägg till>Nytt ställningsobjekt.
I dialogrutan Lägg till nytt genererat objekt väljer du Installerat>Gemensamt>Blazor>Razor Komponent>Razor Komponenter som använder Entity Framework (CRUD). Välj knappen Lägg till.
I dialogrutan Lägg till Razor-komponenter med hjälp av Entity Framework (CRUD) använder du följande inställningar:
- Template: Använd standardvalet (CRUD).
-
Modellklass: Välj den
Contact
modellen i listrutan. -
DbContext-klass: Välj plustecknet (
+
) och välj Lägg till med hjälp av standardkontextklassnamnet som genereras av byggnadsställningen. - Database Provider: Använd standardvalet (SQL Server).
Välj Lägg till för att skapa modellen och skapa SQL Server-databasen.
När scaffolding-åtgärden är klar tar du bort den genererade kontextklassen från mappen Data
(Data/{PROJECT NAME}Context.cs
, där platshållaren {PROJECT NAME}
är projektets namn/namnområde).
Ta bort mappen ContactPages
från mappen Components/Pages
, som innehåller QuickGrid--baserade sidor för kontakthantering. För en fullständig demonstration av QuickGrid-baserade sidor för att hantera data använder du självstudiekursen Skapa en Blazor filmdatabasapp (översikt).
Öppna filen Program
(Program.cs
) och leta reda på raden som scaffolding lade till för att skapa en databasens kontextfabrik med hjälp av SQL Server-leverantören. Ändra kontexten från den genererade kontextklassen (togs bort tidigare) till appens befintliga ContactContext
-klass:
- builder.Services.AddDbContextFactory<BlazorWebAppEFCoreContext>(options =>
+ builder.Services.AddDbContextFactory<ContactContext>(options =>
Nu använder appen SQL Server-providern och en SQL Server-databas som skapats för Contact
-modellklassen. Optimistisk samtidighet fungerar med inbyggda databasgenererade samtidighetstoken som redan har implementerats i exempelappens ContactContext
-klass.
Databasloggning
Exemplet konfigurerar även databasloggning för att visa DE SQL-frågor som genereras. Detta konfigureras i 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"
}
}
}
Komponenterna grid, add och view använder mönstret "context-per-operation", där en kontext skapas för varje åtgärd. Redigeringskomponenten använder mönstret "context-per-component", där en kontext skapas för varje komponent.
Not
Vissa av kodexemplen i det här avsnittet kräver namnområden och tjänster som inte visas. För att inspektera den fullt fungerande koden, inklusive obligatoriska @using
- och @inject
-direktiv för Razor-exempel, se exempelappen.
Guida till att bygga en Blazor filmdatabasapp
En självstudiekurs om hur du skapar en app som använder EF Core för att arbeta med en databas finns i Skapa en Blazor filmdatabasapp (översikt). Handledningen visar hur du skapar en Blazor Web App som kan visa och hantera filmer i en filmdatabas.
Databasåtkomst
EF Core förlitar sig på en DbContext som ett sätt att konfigurera databasåtkomst och fungera som en arbetsenhet. EF Core tillhandahåller AddDbContext-tillägget för ASP.NET Core-appar som registrerar kontexten som en begränsad tjänst. I Blazor appar på serversidan kan begränsade tjänstregistreringar vara problematiska eftersom instansen delas mellan komponenter i användarens krets. DbContext är inte trådsäkert och är inte utformat för samtidig användning. De befintliga livslängderna är olämpliga av följande skäl:
- Singleton delar tillstånd för alla användare av appen och leder till olämplig samtidig användning.
- Scoped (standard) utgör ett liknande problem mellan komponenter för samma användare.
- Tillfälliga resulterar i en ny instans per begäran. men eftersom komponenter kan vara långlivade resulterar detta i en kontext med längre livslängd än vad som kan vara avsett.
Följande rekommendationer är utformade för att ge en konsekvent metod för att använda EF Core i Blazor appar på serversidan.
Överväg att använda en kontext per åtgärd. Kontexten är utformad för snabb instansiering med låg belastning.
using var context = new MyContext(); return await context.MyEntities.ToListAsync();
Använd en flagga för att förhindra flera samtidiga åtgärder:
if (Loading) { return; } try { Loading = true; ... } finally { Loading = false; }
Placera åtgärder efter raden
Loading = true;
itry
-blocket.Trådsäkerhet är inte ett problem, så inläsningslogik kräver inte låsning av databasposter. Inläsningslogik används för att inaktivera användargränssnittskontroller så att användarna inte oavsiktligt väljer knappar eller uppdaterar fält när data hämtas.
Om det finns en risk att flera trådar får åtkomst till samma kodblock, injicera en fabrik och skapa en ny instans per åtgärd. Annars räcker det vanligtvis att mata in och använda kontexten.
För åtgärder med längre livslängd som utnyttjar EF Coreändringsspårning eller samtidighetskontrollomfångskontexten till komponentens livslängd.
Nya DbContext
instanser
Det snabbaste sättet att skapa en ny DbContext instans är att använda new
för att skapa en ny instans. Det finns dock scenarier som kräver att ytterligare beroenden löses.
- Använd
DbContextOptions
för att konfigurera kontexten. - Använda en anslutningssträng per DbContext, till exempel när du använder ASP.NET Cores Identity modell. Mer information finns i Multi-tenancy (EF Core dokumentation).
Varning
Lagra inte apphemligheter, anslutningssträngar, autentiseringsuppgifter, lösenord, personliga identifieringsnummer (PIN),privat C#/.NET-kod eller privata nycklar/token i kod på klientsidan, vilket är alltid osäker. I test-/mellanlagrings- och produktionsmiljöer bör Blazor kod på serversidan och webb-API:er använda säkra autentiseringsflöden som undviker att underhålla autentiseringsuppgifter i projektkod eller konfigurationsfiler. Förutom testning av lokal utveckling rekommenderar vi att du undviker användning av miljövariabler för att lagra känsliga data, eftersom miljövariabler inte är den säkraste metoden. För testning av lokal utveckling rekommenderas verktyget Secret Manager för att skydda känsliga data. Mer information finns i På ett säkert sätt underhålla känsliga data och autentiseringsuppgifter.
Den rekommenderade metoden för att skapa en ny DbContext med beroenden är att använda en fabrik. EF Core 5.0 eller senare erbjuder en inbyggd fabrik för att skapa nya kontexter.
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);
}
}
I föregående fabrik:
- ActivatorUtilities.CreateInstance uppfyller eventuella beroenden via tjänstleverantören.
- IDbContextFactory<TContext> är tillgängligt i EF Core ASP.NET Core 5.0 eller senare, så gränssnittet implementeras i exempelappen för ASP.NET Core 3.x.
I följande exempel konfigureras SQLite- och aktiverar dataloggning. Koden använder en tilläggsmetod (AddDbContextFactory
) för att konfigurera databasfabriken för DI och ange standardalternativ:
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"));
Fabriken matas in i komponenter och används för att skapa nya DbContext
instanser.
På home sidan i exempelappen matas IDbContextFactory<ContactContext>
in i komponenten:
@inject IDbContextFactory<ContactContext> DbFactory
En DbContext
skapas med hjälp av fabriken (DbFactory
) för att ta bort en kontakt i metoden 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();
}
Not
Filters
är en inmatad IContactFilters
och Wrapper
är en komponentreferens till komponenten GridWrapper
. Se Home
komponenten (Components/Pages/Home.razor
) i exempelappen.
Anteckning
Filters
är en inmatad IContactFilters
och Wrapper
är en komponentreferens till komponenten GridWrapper
. Se Index
komponenten (Pages/Index.razor
) i exempelappen.
Räckvidd för komponentens livslängd
Du kanske vill skapa en DbContext som finns under en komponents livslängd. På så sätt kan du använda den som en arbetsenhet och dra nytta av inbyggda funktioner, till exempel ändringsspårning och samtidighetsmatchning.
Du kan använda fabriken för att skapa en kontext och spåra den under komponentens livslängd. Börja med att implementera IDisposable och injicera fabriken som visas i EditContact
-komponenten (Components/Pages/EditContact.razor
):
Du kan använda fabriken för att skapa en kontext och spåra den under komponentens livslängd. Börja med att implementera IDisposable och injicera fabriken som visas i EditContact
-komponenten (Pages/EditContact.razor
):
@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory
Exempelappen ser till att kontexten tas bort när komponenten tas bort:
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();
}
Slutligen åsidosättas OnInitializedAsync
för att skapa en ny kontext. I exempelappen läser OnInitializedAsync
in kontakten med samma 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;
}
}
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();
}
I föregående exempel:
- När
Busy
är inställt påtrue
kan asynkrona åtgärder börja. NärBusy
återställs tillfalse
bör de asynkrona åtgärderna vara slutförda. - Placera ytterligare felhanteringslogik i ett
catch
block.
Aktivera loggning av känsliga data
EnableSensitiveDataLogging innehåller programdata i undantagsmeddelanden och ramverksloggning. De loggade data kan innehålla värden som tilldelats egenskaper för entitetsinstanser och parametervärden för kommandon som skickas till databasen. Loggning av data med EnableSensitiveDataLogging är en säkerhetsriskeftersom det kan exponera lösenord och andra personligt identifierbar information (PII) när SQL-instruktioner som körs mot databasen loggas.
Vi rekommenderar att du bara aktiverar EnableSensitiveDataLogging för utveckling och testning:
#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
Ytterligare resurser
ASP.NET Core