2. část: Razor Stránky s EF Core v ASP.NET Core – CRUD

Tom Dykstra, Vychytáj Liknessa Jon P Smith

Webová aplikace Contoso University vám ukáže, jak vytvářet Razor stránky webových aplikací pomocí EF Core a Visual Studio. Informace o řadě kurzů najdete v prvním kurzu.

Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte dokončenou aplikaci a porovnejte tento kód s tím, co jste vytvořili, pomocí tohoto kurzu.

V tomto kurzu se kód CRUD (vytvoření, čtení, aktualizace, odstranění) vy uživatelského rozhraní zhodnotí a přizpůsobí.

Žádné úložiště

Někteří vývojáři používají vrstvu služby nebo model úložiště k vytvoření abstraktní vrstvy mezi uživatelským rozhraním ( Razor stránkami) a vrstvou přístupu k datům. V tomto kurzu to dělat nemůže. Pro minimalizaci složitosti a zaměření kurzu na EF Core EF Core se kód přidá přímo do tříd modelu stránky.

Aktualizace stránky s podrobnostmi

Kód pro stránky Students (Studenti) s generováním uživatelského rozhraní nezahrnuje registrační data. V této části se registrace přidávají na Details stránku.

Čtení registrací

Pokud chcete na stránce zobrazit data registrace studenta, musí se načíst data o registraci. Kód v souboru Pages/Students/Details.cshtml.cs čte Student pouze data bez těchto Enrollment dat:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Nahraďte OnGetAsync metodu následujícím kódem, který přečte data registrace vybraného studenta. Změny jsou zvýrazněné.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Metody Include a ThenInclude způsobí, že kontext načte navigační vlastnost a v rámci každé registrace Student.Enrollments vlastnost Enrollment.Course navigace. Tyto metody jsou podrobně prozkoumány v kurzu Čtení souvisejících dat.

Metoda AsNoTracking vylepšuje výkon ve scénářích, kdy vrácené entity nejsou v aktuálním kontextu aktualizovány. AsNoTracking je popsán dále v tomto kurzu.

Zobrazení registrací

Nahraďte kód v Pages/Students/Details.cshtml následujícím kódem, který zobrazí seznam registrací. Změny jsou zvýrazněné.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Předchozí kód prochází entity ve vlastnosti Enrollments navigation. U každé registrace se zobrazí název kurzu a známka. Název kurzu se načte z entity, která je uložená v navigační vlastnosti entity Course Course Enrollments( Registrace).

Spusťte aplikaci, vyberte kartu Students (Studenti) a klikněte na odkaz Details (Podrobnosti) pro studenta. Zobrazí se seznam kurzů a známek vybraného studenta.

Způsoby čtení jedné entity

Vygenerovaný kód používá FirstOrDefaultAsync ke čtení jedné entity. Tato metoda vrátí hodnotu null, pokud se nic nenašlo. V opačném případě vrátí první nalezený řádek, který splňuje kritéria filtru dotazu. FirstOrDefaultAsync je obecně lepší volbou než následující alternativy:

  • SingleOrDefaultAsync – vyvolá výjimku, pokud existuje více než jedna entita, která splňuje filtr dotazu. Pokud chcete zjistit, jestli dotaz může vrátit více než jeden řádek, SingleOrDefaultAsync pokusí se načíst více řádků. Tato práce navíc není nutná, pokud dotaz může vrátit pouze jednu entitu, jako když hledá jedinečný klíč.
  • FindAsync – vyhledá entitu s primárním klíčem (PK). Pokud je entita s pk sledována kontextem, vrátí se bez požadavku do databáze. Tato metoda je optimalizovaná pro vyhledávání jedné entity, ale nelze ji volat Include pomocí FindAsync . Takže pokud jsou potřeba související data, FirstOrDefaultAsync je lepší volbou.

Směrování dat vs. řetězec dotazu

Adresa URL stránky Podrobnosti je https://localhost:<port>/Students/Details?id=1 . Hodnota primárního klíče entity je v řetězci dotazu. Někteří vývojáři dávají přednost předání hodnoty klíče v datech tras: https://localhost:<port>/Students/Details/1 . Další informace najdete v tématu Aktualizace generovaného kódu.

Aktualizace stránky Vytvořit

Kód pro generování kódu na stránce Vytvořit je zranitelný OnPostAsync vůči přeúčtování. Nahraďte OnPostAsync metodu v Pages/Students/Create.cshtml.cs následujícím kódem.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Předchozí kód vytvoří objekt Student a pak použije pole formuláře posted k aktualizaci vlastností objektu Student. Metoda TryUpdateModelAsync:

  • Použije hodnoty formuláře posted z vlastnosti PageContext v modelu PageModel.
  • Aktualizuje pouze uvedené vlastnosti ( s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate ).
  • Vyhledá pole formuláře s předponou studenta. Například, Student.FirstMidName. Rozlišují se v ní malá a velká písmena.
  • Používá systém vazby modelu k převodu hodnot formuláře z řetězců na typy v Student modelu. Například je EnrollmentDate převeden na DateTime .

Spusťte aplikaci a vytvořte entitu studenta, která otestuje stránku Vytvořit.

Overposting

Použití k aktualizaci polí s posted hodnotami je TryUpdateModel osvědčeným postupem zabezpečení, protože brání přeúčtování. Předpokládejme například, že entita Student obsahuje vlastnost, kterou by tato webová stránka neměla Secret aktualizovat ani přidávat:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

I když aplikace nemá pole na stránce pro vytvoření nebo aktualizaci, hacker by mohl hodnotu nastavit tak, že ji Secret Razor Secret přesídlí. Hacker může k zobrazení hodnoty formuláře použít nástroj, jako je Fiddler, nebo napsat nějaký Secret JavaScript. Původní kód nemeže pole, která modelový važeř používá při vytváření instance Student.

Bez ohledu na to, jestli se v databázi aktualizuje hodnota Secret zadaná hackerem pro pole formuláře. Následující obrázek znázorňuje nástroj Fiddler, který do hodnot v posted form přidá pole s hodnotou Secret OverPost.

Fiddler adding Secret field

Hodnota OverPost se úspěšně přidala do Secret vlastnosti vložili řádku. K tomu dochází i v případě, že návrhář aplikace nikdy nezamýšlel nastavení Secret vlastnosti pomocí stránky Vytvořit.

Model zobrazení

Zobrazení modelů nabízí alternativní způsob, jak zabránit přeúčtování.

Aplikační model se často nazývá doménový model. Doménový model obvykle obsahuje všechny vlastnosti vyžadované odpovídající entitou v databázi. Model zobrazení obsahuje pouze vlastnosti potřebné pro stránku uživatelského rozhraní, například stránku Vytvořit.

Kromě modelu zobrazení některé aplikace používají model vazby nebo vstupní model k předání dat mezi třídou modelu stránky Stránky a Razor prohlížečem.

Vezměte v úvahu StudentVM následující model zobrazení:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Následující kód používá model StudentVM zobrazení k vytvoření nového studenta:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Metoda SetValues nastavuje hodnoty tohoto objektu čtením hodnot z jiného objektu PropertyValues. SetValues používá porovnávání názvů vlastností. Typ modelu zobrazení:

  • Nemusí souviset s typem modelu.
  • Musí mít vlastnosti, které se shodují.

Použití StudentVM příkazu vyžaduje použití stránky Vytvořit místo StudentVM Student :

@page
@model CreateVMModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Student</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Aktualizace stránky Pro úpravy

V souboru Pages/Students/Edit.cshtml.cs nahraďte metody OnGetAsync a následujícím OnPostAsync kódem.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Změny kódu se podobají stránce Vytvořit s několika výjimkami:

  • FirstOrDefaultAsync byla nahrazena metodou FindAsync. Pokud nepotřebujete zahrnout související data, FindAsync je efektivnější.
  • OnPostAsyncid parametr .
  • Aktuální student se načte z databáze místo vytvoření prázdného studenta.

Spusťte aplikaci a otestujte ji vytvořením a úpravou studenta.

Stavy entit

Kontext databáze sleduje, jestli jsou entity v paměti synchronizované s odpovídajícími řádky v databázi. Tyto informace o sledování určují, co se stane, když se volá SaveChangesAsync. Když se například do metody AddAsync předá nová entita, stav této entity se nastaví na Přidané. Při SaveChangesAsync volání vydá kontext databáze SQL INSERT příkaz.

Entita může být v jednom z následujících stavů:

  • Added: Entita ještě v databázi neexistuje. Metoda SaveChanges vydá INSERT příkaz .

  • Unchanged: U této entity není nutné ukládat žádné změny. Entita má tento stav při čtení z databáze.

  • Modified: Změnily se některé nebo všechny hodnoty vlastností entity. SaveChangesMetoda vydá UPDATE příkaz.

  • Deleted: Entita byla označena pro odstranění. SaveChangesMetoda vydá DELETE příkaz.

  • Detached: Entita není sledována kontextem databáze.

V desktopové aplikaci se změny stavu obvykle nastaví automaticky. Entita je čtena, změny jsou provedeny a stav entity se automaticky změní na Modified . volání SaveChanges generuje příkaz SQL UPDATE , který aktualizuje pouze změněné vlastnosti.

Ve webové aplikaci, DbContext která čte entitu a zobrazuje data, jsou uvolněna po vykreslení stránky. Když OnPostAsync je volána metoda stránky, je vytvořena nová webová žádost a s novou instancí DbContext . Přečtení entity v tomto novém kontextu simuluje zpracování plochy.

Aktualizace stránky pro odstranění

V této části je vlastní chybová zpráva implementována v případě, že se volání SaveChanges nezdařilo.

Nahraďte kód na stránkách/Students/DELETE. cshtml. cs následujícím kódem:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<DeleteModel> _logger;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context,
                           ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, ErrorMessage);

                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Předchozí kód:

  • Přidá protokolování.
  • Přidá do saveChangesError OnGetAsync podpisu metody volitelný parametr. saveChangesError označuje, zda byla metoda volána po neúspěšném odstranění objektu student.

Operace odstranění může selhat kvůli přechodným problémům se sítí. Přechodné chyby sítě jsou pravděpodobnější, když je databáze v cloudu. saveChangesErrorParametr je false při volání stránky Delete OnGetAsync z uživatelského rozhraní. Když OnGetAsync je volána nástrojem OnPostAsync , protože operace odstranění se nezdařila, saveChangesError parametr je true .

OnPostAsyncMetoda načte vybranou entitu a pak zavolá metodu Remove pro nastavení stavu entity na Deleted . když SaveChanges je volána, DELETE je vygenerován příkaz SQL. Pokud se chyba Remove nezdařila:

  • Je zachycena výjimka databáze.
  • Metoda Delete Pages OnGetAsync je volána s saveChangesError=true .

Přidat chybovou zprávu na stránky/studenty/odstranit. cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Spusťte aplikaci a odstraňte studenta a otestujte stránku odstranit.

Další kroky

V tomto kurzu se vyhodnotí a přizpůsobí se vygenerovaný kód CRUD (vytváření, čtení, aktualizace, odstranění).

Žádné úložiště

Někteří vývojáři používají vrstvu služby nebo model úložiště k vytvoření abstrakce vrstvy mezi uživatelským rozhraním ( Razor stránky) a vrstvou přístupu k datům. V tomto kurzu tento kurz neudělá. K minimalizaci složitosti a udržování kurzu zaměřeného na EF Core je EF Core kód přidán přímo do tříd modelu stránky.

Aktualizace stránky s podrobnostmi

Generovaný kód pro stránky studentů neobsahuje registrační data. V této části se na stránku podrobností přidají registrace.

Čtení zápisů

Chcete-li zobrazit data o registraci studenta na stránce, je nutné přečíst data zápis. Generovaný kód na stránkách/Students/details. cshtml. cs čte pouze data studenta bez registračních údajů:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Nahraďte OnGetAsync metodu následujícím kódem pro čtení dat zápisu pro vybraného studenta. Změny jsou zvýrazněny.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Metody include a ThenInclude způsobují, že kontext načte Student.Enrollments vlastnost navigace a v rámci každé registrace Enrollment.Course vlastnost navigace. Tyto metody jsou podrobně prověřeny v kurzu čtení souvisejících dat .

Metoda AsNoTracking vylepšuje výkon ve scénářích, kde vracené entity nejsou aktualizovány v aktuálním kontextu. AsNoTracking je popsána dále v tomto kurzu.

Zobrazit registrace

Nahraďte kód na stránkách/Students/details. cshtml pomocí následujícího kódu pro zobrazení seznamu zápisů. Změny jsou zvýrazněny.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Předchozí kód projde entitami v Enrollments navigační vlastnosti. U každého zápisu se zobrazí titul kurzu a stupeň. Název kurzu se načte z entity kurzu, která je uložená v Course navigační vlastnosti entity registrace.

Spusťte aplikaci, vyberte kartu Students a klikněte na odkaz Podrobnosti pro studenta. Zobrazí se seznam kurzů a stupňů pro vybraného studenta.

Způsoby, jak číst jednu entitu

Vygenerovaný kód používá FirstOrDefaultAsync ke čtení jedné entity. Tato metoda vrátí hodnotu null, pokud není nic nalezeno. v opačném případě vrátí první nalezený řádek, který splňuje kritéria filtru dotazu. FirstOrDefaultAsync je všeobecně lepší volbou než u následujících alternativ:

  • SingleOrDefaultAsync – vyvolá výjimku, pokud existuje více než jedna entita, která vyhovuje filtru dotazu. Chcete-li zjistit, zda dotaz vrátil více než jeden řádek, nástroj se SingleOrDefaultAsync pokusí načíst více řádků. Tato doplňková práce není nutná, pokud dotaz může vracet pouze jednu entitu, stejně jako při vyhledávání jedinečného klíče.
  • FindAsync – najde entitu s primárním klíčem (PK). Pokud je entita s PK sledována kontextem, je vrácena bez požadavku na databázi. Tato metoda je optimalizovaná pro vyhledání jedné entity, ale nemůžete volat Include pomocí FindAsync . Takže pokud jsou potřeba související data, FirstOrDefaultAsync je lepší volbou.

Směrování dat a řetězce dotazu

Adresa URL stránky podrobností je https://localhost:<port>/Students/Details?id=1 . Hodnota primárního klíče entity je v řetězci dotazu. Někteří vývojáři dávají přednost předávání hodnoty klíče v datech směrování: https://localhost:<port>/Students/Details/1 . Další informace najdete v tématu aktualizace generovaného kódu.

Aktualizace stránky pro vytvoření

Vygenerovaný OnPostAsync kód stránky pro vytváření je zranitelný vůči přestavení. Nahraďte OnPostAsync metodu na stránkách/Students/Create. cshtml. cs následujícím kódem.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Předchozí kód vytvoří objekt studenta a pak použije odeslaná pole formuláře k aktualizaci vlastností objektu studenta. Metoda TryUpdateModelAsync :

  • Používá hodnoty odeslaného formuláře z vlastnosti PageContext v PageModel.
  • Aktualizuje pouze uvedené vlastnosti ( s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate ).
  • Vyhledá pole formuláře s předponou "student". Například, Student.FirstMidName. Nerozlišuje velká a malá písmena.
  • Používá systém vázání modelů k převodu hodnot formuláře z řetězců na typy v Student modelu. Například EnrollmentDate je třeba převést na typ DateTime.

Spusťte aplikaci a vytvořte entitu studenta k otestování stránky vytvořit.

Přestavování

Použití TryUpdateModel k aktualizaci polí pomocí publikovaných hodnot je osvědčeným postupem zabezpečení, protože brání přestavení. Předpokládejme například, že entita student zahrnuje Secret vlastnost, kterou by tato webová stránka neměla aktualizovat ani přidat:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

I v případě, že aplikace neobsahuje Secret pole na stránce Vytvořit nebo aktualizovat Razor , hacker by mohl Secret hodnotu nastavit pomocí přestavení. Počítačový podvodník může k odeslání hodnoty formuláře použít nějaký nástroj, jako je například Fiddler, nebo napsat nějaký JavaScript Secret . Původní kód neomezuje pole, která používá pořadač modelů při vytváření instance studenta.

Bez ohledu na hodnotu se v databázi aktualizuje počítačový podvodník zadaný pro Secret pole formuláře. Následující obrázek znázorňuje nástroj Fiddler, který přidá Secret pole (s hodnotou "Repost") do publikovaných hodnot formuláře.

Fiddler přidání tajného pole

Hodnota "přeúčtování" se úspěšně přidala do Secret Vlastnosti vloženého řádku. K tomu dochází i v případě, že návrhář aplikace nikdy nezamýšlel vlastnost, která Secret má být nastavena pomocí stránky vytvořit.

Model zobrazení

Zobrazení modelů nabízí alternativní způsob, jak zabránit přeúčtování.

Aplikační model se často nazývá doménový model. Doménový model obvykle obsahuje všechny vlastnosti vyžadované odpovídající entitou v databázi. Model zobrazení obsahuje pouze vlastnosti potřebné pro uživatelské rozhraní, pro které se používá (například stránka Vytvořit).

Kromě modelu zobrazení některé aplikace používají model vazby nebo vstupní model k předání dat mezi třídou modelu stránky Stránky a Razor prohlížečem.

Vezměte v úvahu Student následující model zobrazení:

using System;

namespace ContosoUniversity.Models
{
    public class StudentVM
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

Následující kód používá model StudentVM zobrazení k vytvoření nového studenta:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Metoda SetValues nastavuje hodnoty tohoto objektu čtením hodnot z jiného objektu PropertyValues. SetValues používá porovnávání názvů vlastností. Typ modelu zobrazení nemusí souviset s typem modelu, stačí, aby měl odpovídající vlastnosti.

Použití StudentVM vyžaduje aktualizaci souboru Create.cshtml tak, aby StudentVM se místo Student .

Aktualizace stránky Pro úpravy

V souboru Pages/Students/Edit.cshtml.cs nahraďte metody OnGetAsync a následujícím OnPostAsync kódem.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Změny kódu se podobají stránce Vytvořit s několika výjimkami:

  • FirstOrDefaultAsync byla nahrazena metodou FindAsync. Pokud zahrnutá související data nejsou potřeba, FindAsync je efektivnější.
  • OnPostAsyncid parametr .
  • Aktuální student se načte z databáze místo vytvoření prázdného studenta.

Spusťte aplikaci a otestujte ji vytvořením a úpravou studenta.

Stavy entit

Kontext databáze sleduje, jestli jsou entity v paměti synchronizované s odpovídajícími řádky v databázi. Tyto informace o sledování určují, co se stane, když se volá SaveChangesAsync. Když se například do metody AddAsync předá nová entita, stav této entity se nastaví na Přidané. Při SaveChangesAsync volání příkazu INSERT kontext databáze vydá SQL INSERT.

Entita může být v jednom z následujících stavů:

  • Added: Entita ještě v databázi neexistuje. Metoda SaveChanges vydá příkaz INSERT.

  • Unchanged: U této entity není potřeba ukládat žádné změny. Entita má tento stav při čtení z databáze.

  • Modified: Změnily se některé nebo všechny hodnoty vlastností entity. Metoda SaveChanges vydá příkaz UPDATE.

  • Deleted: Entita byla označena k odstranění. Metoda SaveChanges vydá příkaz DELETE.

  • Detached: Entitu nesleduje kontext databáze.

V desktopové aplikaci se změny stavu obvykle nastavují automaticky. Entita se přečte, změny se promísí a stav entity se automaticky změní na Modified . Volání SaveChanges vygeneruje SQL UPDATE, který aktualizuje pouze změněné vlastnosti.

Ve webové aplikaci je objekt , který čte entitu a zobrazuje data, odstraněn po DbContext vykreslení stránky. Když je volána metoda stránky, je proveden nový webový požadavek a s OnPostAsync novou instancí DbContext objektu . Přepracování entity v tomto novém kontextu simuluje zpracování plochy.

Aktualizace stránky Odstranit

V této části implementujete vlastní chybovou zprávu, když volání SaveChanges selže.

Nahraďte kód v pages/students/delete.cshtml.cs následujícím kódem. Změny se zvýrazní (kromě vyčištění using příkazů).

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = "Delete failed. Try again";
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Předchozí kód přidá volitelný parametr saveChangesError k podpisu OnGetAsync metody. saveChangesError určuje, jestli byla metoda volána po selhání odstranění objektu studenta. Operace odstranění může selhat kvůli přechodným problémům se sítí. Přechodné chyby sítě jsou pravděpodobnější, když je databáze v cloudu. Parametr saveChangesError je nepravdivý, pokud je stránka Delete OnGetAsync volána z uživatelského rozhraní. Při OnGetAsync volání metody OnPostAsync (protože operace odstranění selhala) saveChangesError má parametr hodnotu true.

Metoda OnPostAsync načte vybranou entitu a potom zavolá metodu Remove, která nastaví stav entity na Deleted . Při SaveChanges volání se vygeneruje SQL DELETE. Pokud Remove selže:

  • Je zachycena výjimka databáze.
  • Metoda odstranění stránky se OnGetAsync volá pomocí saveChangesError=true .

Přidejte na stránku pro odstranění Razor chybovou zprávu (Pages/Students/Delete.cshtml):

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Spusťte aplikaci a odstraňte studenta a otestujte stránku Odstranit.

Další kroky

V tomto kurzu se kód CRUD (vytvoření, čtení, aktualizace, odstranění) vy uživatelského rozhraní zhodnotí a přizpůsobí.

Žádné úložiště

Někteří vývojáři používají vrstvu služby nebo model úložiště k vytvoření abstraktní vrstvy mezi uživatelským rozhraním ( Razor stránkami) a vrstvou přístupu k datům. V tomto kurzu to dělat nemůže. Pro minimalizaci složitosti a zaměření kurzu na EF Core EF Core se kód přidá přímo do tříd modelu stránky.

Aktualizace stránky s podrobnostmi

Kód pro stránky Students (Studenti) s generováním uživatelského rozhraní nezahrnuje data o registraci. V této části se registrace přidávají na Details stránku.

Čtení registrací

Pokud chcete na stránce zobrazit data registrace studenta, musí se načíst data o registraci. Kód v souboru Pages/Students/Details.cshtml.cs čte Student pouze data bez těchto Enrollment dat:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Nahraďte OnGetAsync metodu následujícím kódem, který přečte data registrace vybraného studenta. Změny jsou zvýrazněné.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Metody Include a ThenInclude způsobí, že kontext načte navigační vlastnost a v rámci každé registrace Student.Enrollments vlastnost Enrollment.Course navigace. Tyto metody jsou podrobně prozkoumány v kurzu Čtení souvisejících dat.

Metoda AsNoTracking vylepšuje výkon ve scénářích, kdy vrácené entity nejsou v aktuálním kontextu aktualizovány. AsNoTracking je popsán dále v tomto kurzu.

Zobrazení registrací

Nahraďte kód v Pages/Students/Details.cshtml následujícím kódem, který zobrazí seznam registrací. Změny jsou zvýrazněné.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Předchozí kód prochází entity ve vlastnosti Enrollments navigation. U každé registrace se zobrazí název kurzu a známka. Název kurzu se načte z entity, která je uložená v navigační vlastnosti entity Course Course Enrollments( Registrace).

Spusťte aplikaci, vyberte kartu Students (Studenti) a klikněte na odkaz Details (Podrobnosti) pro studenta. Zobrazí se seznam kurzů a známek vybraného studenta.

Způsoby čtení jedné entity

Vygenerovaný kód používá FirstOrDefaultAsync ke čtení jedné entity. Tato metoda vrátí hodnotu null, pokud se nic nenašlo. V opačném případě vrátí první nalezený řádek, který splňuje kritéria filtru dotazu. FirstOrDefaultAsync je obecně lepší volbou než následující alternativy:

  • SingleOrDefaultAsync – vyvolá výjimku, pokud existuje více než jedna entita, která splňuje filtr dotazu. Pokud chcete zjistit, jestli dotaz může vrátit více než jeden řádek, SingleOrDefaultAsync pokusí se načíst více řádků. Tato práce navíc není nutná, pokud dotaz může vrátit pouze jednu entitu, jako když hledá jedinečný klíč.
  • FindAsync – vyhledá entitu s primárním klíčem (PK). Pokud je entita s pk sledována kontextem, vrátí se bez požadavku do databáze. Tato metoda je optimalizovaná pro vyhledávání jedné entity, ale nelze ji volat Include pomocí FindAsync . Takže pokud jsou potřeba související data, FirstOrDefaultAsync je lepší volbou.

Směrování dat vs. řetězec dotazu

Adresa URL stránky Podrobnosti je https://localhost:<port>/Students/Details?id=1 . Hodnota primárního klíče entity je v řetězci dotazu. Někteří vývojáři dávají přednost předání hodnoty klíče v datech tras: https://localhost:<port>/Students/Details/1 . Další informace najdete v tématu Aktualizace generovaného kódu.

Aktualizace stránky pro vytvoření

Vygenerovaný OnPostAsync kód stránky pro vytváření je zranitelný vůči přestavení. Nahraďte OnPostAsync metodu na stránkách/Students/Create. cshtml. cs následujícím kódem.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Předchozí kód vytvoří objekt studenta a pak použije odeslaná pole formuláře k aktualizaci vlastností objektu studenta. Metoda TryUpdateModelAsync :

  • Používá hodnoty odeslaného formuláře z vlastnosti PageContext v PageModel.
  • Aktualizuje pouze uvedené vlastnosti ( s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate ).
  • Vyhledá pole formuláře s předponou "student". Například, Student.FirstMidName. Nerozlišuje velká a malá písmena.
  • Používá systém vázání modelů k převodu hodnot formuláře z řetězců na typy v Student modelu. Například EnrollmentDate je převeden na DateTime .

Spusťte aplikaci a vytvořte entitu studenta k otestování stránky vytvořit.

Přestavování

Použití TryUpdateModel k aktualizaci polí pomocí publikovaných hodnot je osvědčeným postupem zabezpečení, protože brání přestavení. Předpokládejme například, že entita student zahrnuje Secret vlastnost, kterou by tato webová stránka neměla aktualizovat ani přidat:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

I v případě, že aplikace neobsahuje Secret pole na stránce Vytvořit nebo aktualizovat Razor , hacker by mohl Secret hodnotu nastavit pomocí přestavení. Počítačový podvodník může k odeslání hodnoty formuláře použít nějaký nástroj, jako je například Fiddler, nebo napsat nějaký JavaScript Secret . Původní kód neomezuje pole, která používá pořadač modelů při vytváření instance studenta.

Bez ohledu na hodnotu se v databázi aktualizuje počítačový podvodník zadaný pro Secret pole formuláře. Na následujícím obrázku je znázorněn nástroj Fiddler, který přidá Secret pole s hodnotou "Repost" (hodnota "" "") na zaúčtované hodnoty formuláře.

Fiddler přidání tajného pole

Hodnota "přeúčtování" se úspěšně přidala do Secret Vlastnosti vloženého řádku. K tomu dochází i v případě, že návrhář aplikace nikdy nezamýšlel vlastnost, která Secret má být nastavena pomocí stránky vytvořit.

Model zobrazení

Modely zobrazení poskytují alternativní způsob, jak zabránit přestavení.

Aplikační model se často označuje jako doménový model. Doménový model obvykle obsahuje všechny vlastnosti požadované odpovídající entitou v databázi. Model zobrazení obsahuje pouze vlastnosti, které jsou potřeba pro stránku uživatelského rozhraní, například na stránce vytvořit.

Kromě modelu zobrazení některé aplikace používají model vazby nebo vstupní model k předávání dat mezi Razor třídou modelu stránky stránky a prohlížečem.

Vezměte v úvahu následující StudentVM model zobrazení:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Následující kód používá StudentVM model zobrazení k vytvoření nového studenta:

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Metoda NastavitHodnotu nastaví hodnoty tohoto objektu čtením hodnot z jiného objektu PropertyValues . SetValues používá spárování názvů vlastností. Typ modelu zobrazení:

  • Nemusí být v souvislosti s typem modelu.
  • Musí mít odpovídající vlastnosti.

Použití StudentVM vyžaduje použití stránky StudentVM místo Student :

@page
@model CreateVMModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Student</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Aktualizace stránky pro úpravy

Na stránce Pages/Students/Edit. cshtml. cs, nahraďte OnGetAsync OnPostAsync metody a následujícím kódem.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Změny kódu jsou podobné na stránce vytvořit s několika výjimkami:

  • FirstOrDefaultAsync byl nahrazen pomocí FindAsync. Pokud nepotřebujete zahrnout související data, FindAsync je efektivnější.
  • OnPostAsyncid parametr.
  • Aktuální student se načte z databáze místo vytvoření prázdného studenta.

Spusťte aplikaci a otestujte ji vytvořením a úpravou studenta.

Stavy entit

Kontext databáze uchovává přehled o tom, zda jsou entity v paměti synchronizovány s odpovídajícími řádky v databázi. Tyto informace o sledování určují, co se stane, když se zavolá SaveChangesAsync . Například při předání nové entity metodě AddAsync se stav této entity nastaví na přidáno. když SaveChangesAsync je volána, kontext databáze vydá INSERT příkaz SQL.

Entita může být v jednom z následujících stavů:

  • Added: Entita v databázi ještě neexistuje. SaveChangesMetoda vydá INSERT příkaz.

  • Unchanged: U této entity není nutné ukládat žádné změny. Entita má tento stav při čtení z databáze.

  • Modified: Změnily se některé nebo všechny hodnoty vlastností entity. SaveChangesMetoda vydá UPDATE příkaz.

  • Deleted: Entita byla označena pro odstranění. SaveChangesMetoda vydá DELETE příkaz.

  • Detached: Entita není sledována kontextem databáze.

V desktopové aplikaci se změny stavu obvykle nastaví automaticky. Entita je čtena, změny jsou provedeny a stav entity se automaticky změní na Modified . volání SaveChanges generuje příkaz SQL UPDATE , který aktualizuje pouze změněné vlastnosti.

Ve webové aplikaci, DbContext která čte entitu a zobrazuje data, jsou uvolněna po vykreslení stránky. Když OnPostAsync je volána metoda stránky, je vytvořena nová webová žádost a s novou instancí DbContext . Přečtení entity v tomto novém kontextu simuluje zpracování plochy.

Aktualizace stránky pro odstranění

V této části je vlastní chybová zpráva implementována v případě, že se volání SaveChanges nezdařilo.

Nahraďte kód na stránkách/Students/DELETE. cshtml. cs následujícím kódem:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<DeleteModel> _logger;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context,
                           ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, ErrorMessage);

                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Předchozí kód:

  • Přidá protokolování.
  • Přidá do saveChangesError OnGetAsync podpisu metody volitelný parametr. saveChangesError označuje, zda byla metoda volána po neúspěšném odstranění objektu student.

Operace odstranění může selhat kvůli přechodným problémům se sítí. Přechodné chyby sítě jsou pravděpodobnější, když je databáze v cloudu. saveChangesErrorParametr je false při volání stránky Delete OnGetAsync z uživatelského rozhraní. Když OnGetAsync je volána nástrojem OnPostAsync , protože operace odstranění se nezdařila, saveChangesError parametr je true .

OnPostAsyncMetoda načte vybranou entitu a pak zavolá metodu Remove pro nastavení stavu entity na Deleted . když SaveChanges je volána, DELETE je vygenerován příkaz SQL. Pokud se chyba Remove nezdařila:

  • Je zachycena výjimka databáze.
  • Metoda Delete Pages OnGetAsync je volána s saveChangesError=true .

Přidat chybovou zprávu na stránky/studenty/odstranit. cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Spusťte aplikaci a odstraňte studenta a otestujte stránku odstranit.

Další kroky