Sdílet prostřednictvím


ASP.NET Core Blazor s Entity Framework Core (EF Core)

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete v tomto článku ve verzi .NET 9.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální verzi najdete v tomto článku ve verzi .NET 9.

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 v tomto článku ve verzi .NET 9.

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) v systému Blazor Web App.

Tyto pokyny platí pro Server projekt hostovaného Blazor WebAssembly řešení nebo Blazor Server aplikace.

Zabezpečený tok ověřování vyžadovaný pro produkční aplikace

Tento článek používá místní databázi, která nevyžaduje ověření uživatele. Produkční aplikace by měly používat nejbezpečnější dostupný tok ověřování. Další informace o ověřování nasazenýchtestovacích Blazor BlazorIdentity

Pro služby Microsoft Azure doporučujeme používat spravované identity. Spravované identity se bezpečně ověřují ve službách Azure bez uložení přihlašovacích údajů v kódu aplikace. Další informace naleznete v následujících zdrojích:

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. Nativní tokeny souběžnosti generované databází ale nejsou podporovány pro databáze SQLite, což je zprostředkovatel databáze pro ukázkovou aplikaci. Pokud chcete předvést souběžnost s ukázkovou aplikací, přijměte jiného zprostředkovatele databáze, který podporuje tokeny souběžnosti generované databází (například poskytovatele SQL Serveru).

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.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.

Kurz vytvoření Blazor aplikace pro filmovou databázi

Výukové prostředí pro vytváření aplikace, která používá EF Core k práci s databází, najdete v tématu Vytvoření Blazor aplikace filmové databáze (Přehled). V tomto kurzu se dozvíte, jak vytvořit Blazor Web App video, které může zobrazovat a spravovat filmy v databázi filmů.

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é registrují kontext jako vymezenou službu. 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.

  • 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 v try bloku.

    Zabezpečení vláken není problém, takže logika načítání nevyžaduje uzamčení záznamů databáze. 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í:

Upozorňující

Neukládejte tajné kódy aplikací, připojovací řetězec, přihlašovací údaje, hesla, osobní identifikační čísla (PIN), privátní kód C#/.NET nebo privátní klíče/tokeny v kódu na straně klienta, což je vždy nezabezpečené. V testovacích a pracovních a produkčních prostředích by kód na straně Blazor serveru a webová rozhraní API měly používat toky zabezpečeného ověřování, které se vyhýbají údržbě přihlašovacích údajů v kódu projektu nebo konfiguračních souborech. Mimo místní testování vývoje doporučujeme vyhnout se použití proměnných prostředí k ukládání citlivých dat, protože proměnné prostředí nejsou nejbezpečnějším přístupem. Pro místní testování vývoje se pro zabezpečení citlivých dat doporučuje nástroj Secret Manager. Další informace najdete v tématu Bezpečné udržování citlivých dat a přihlašovacích údajů.

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ě:

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"));
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 home 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;

    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ý IContactFiltersa 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ý IContactFiltersa 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();
}
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;
    }
}
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();
}

V předchozím příkladu:

  • Pokud Busy je nastavena na true, asynchronní operace mohou začínat. Po Busy nastavení zpět na falseasynchronní 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

Další materiály