Tutorial: Implementar a funcionalidade CRUD - ASP.NET MVC com EF CoreTutorial: Implement CRUD Functionality - ASP.NET MVC with EF Core

No tutorial anterior, você criou um aplicativo MVC que armazena e exibe dados usando o Entity Framework e o LocalDB do SQL Server.In the previous tutorial, you created an MVC application that stores and displays data using the Entity Framework and SQL Server LocalDB. Neste tutorial, você examinará e personalizará o código CRUD (criar, ler, atualizar e excluir) que o scaffolding do MVC cria automaticamente para você em controladores e exibições.In this tutorial, you'll review and customize the CRUD (create, read, update, delete) code that the MVC scaffolding automatically creates for you in controllers and views.

Observação

É uma prática comum implementar o padrão de repositório para criar uma camada de abstração entre o controlador e a camada de acesso a dados.It's a common practice to implement the repository pattern in order to create an abstraction layer between your controller and the data access layer. Para manter esses tutoriais simples e com foco no ensino de como usar o Entity Framework em si, eles não usam repositórios.To keep these tutorials simple and focused on teaching how to use the Entity Framework itself, they don't use repositories. Para obter informações sobre repositórios com o EF, consulte o último tutorial desta série.For information about repositories with EF, see the last tutorial in this series.

Neste tutorial, você:In this tutorial, you:

  • Personalizar a página DetalhesCustomize the Details page
  • Atualizar a página CriarUpdate the Create page
  • Atualizar a página EditarUpdate the Edit page
  • Atualizar a página ExcluirUpdate the Delete page
  • Fechará conexões de banco de dadosClose database connections

Pré-requisitosPrerequisites

Personalizar a página DetalhesCustomize the Details page

O código gerado por scaffolding da página Índice de Alunos omitiu a propriedade Enrollments, porque essa propriedade contém uma coleção.The scaffolded code for the Students Index page left out the Enrollments property, because that property holds a collection. Na página Detalhes, você exibirá o conteúdo da coleção em uma tabela HTML.In the Details page, you'll display the contents of the collection in an HTML table.

Em Controllers/StudentsController.cs, o método de ação para a exibição Detalhes usa o método SingleOrDefaultAsync para recuperar uma única entidade Student.In Controllers/StudentsController.cs, the action method for the Details view uses the SingleOrDefaultAsync method to retrieve a single Student entity. Adicione um código que chama Include.Add code that calls Include. Os métodos ThenInclude e AsNoTracking, conforme mostrado no código realçado a seguir.ThenInclude, and AsNoTracking methods, as shown in the following highlighted code.

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

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

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

    return View(student);
}

Os métodos Include e ThenInclude fazem com que o contexto carregue a propriedade de navegação Student.Enrollments e, dentro de cada registro, a propriedade de navegação Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Você aprenderá mais sobre esses métodos no tutorial Ler dados relacionados.You'll learn more about these methods in the read related data tutorial.

O método AsNoTracking melhora o desempenho em cenários em que as entidades retornadas não serão atualizadas no tempo de vida do contexto atual.The AsNoTracking method improves performance in scenarios where the entities returned won't be updated in the current context's lifetime. Você aprenderá mais sobre AsNoTracking ao final deste tutorial.You'll learn more about AsNoTracking at the end of this tutorial.

Dados de rotaRoute data

O valor de chave que é passado para o método Details é obtido dos dados de rota.The key value that's passed to the Details method comes from route data. Dados de rota são dados que o associador de modelos encontrou em um segmento da URL.Route data is data that the model binder found in a segment of the URL. Por exemplo, a rota padrão especifica os segmentos de controlador, ação e ID:For example, the default route specifies controller, action, and id segments:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Na URL a seguir, a rota padrão mapeia Instructor como o controlador, Index como a ação e 1 como a ID; esses são valores de dados de rota.In the following URL, the default route maps Instructor as the controller, Index as the action, and 1 as the id; these are route data values.

http://localhost:1230/Instructor/Index/1?courseID=2021

A última parte da URL ("?courseID=2021") é um valor de cadeia de caracteres de consulta.The last part of the URL ("?courseID=2021") is a query string value. O associador de modelos passará o valor da ID para o parâmetro id do método Index se você passá-lo como um valor de cadeia de caracteres de consulta:The model binder will also pass the ID value to the Index method id parameter if you pass it as a query string value:

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

Na página Índice, as URLs de hiperlinks são criadas por instruções de auxiliar de marcação na exibição do Razor.In the Index page, hyperlink URLs are created by tag helper statements in the Razor view. No código Razor a seguir, o parâmetro id corresponde à rota padrão e, portanto, id é adicionada aos dados de rota.In the following Razor code, the id parameter matches the default route, so id is added to the route data.

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

Isso gera o seguinte HTML quando item.ID é 6:This generates the following HTML when item.ID is 6:

<a href="/Students/Edit/6">Edit</a>

No código a seguir Razor, studentID não corresponde a um parâmetro na rota padrão e, portanto, ela é adicionada como uma cadeia de caracteres de consulta.In the following Razor code, studentID doesn't match a parameter in the default route, so it's added as a query string.

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>

Isso gera o seguinte HTML quando item.ID é 6:This generates the following HTML when item.ID is 6:

<a href="/Students/Edit?studentID=6">Edit</a>

Para obter mais informações sobre os auxiliares de marca, confira Auxiliares de Marca no ASP.NET Core.For more information about tag helpers, see Auxiliares de Marca no ASP.NET Core.

Adicionar registros à exibição DetalhesAdd enrollments to the Details view

Abra Views/Students/Details.cshtml.Open Views/Students/Details.cshtml. Cada campo é exibido usando auxiliares DisplayNameFor e DisplayFor, conforme mostrado no seguinte exemplo:Each field is displayed using DisplayNameFor and DisplayFor helpers, as shown in the following example:

<dt class="col-sm-2">
    @Html.DisplayNameFor(model => model.LastName)
</dt>
<dd class="col-sm-10">
    @Html.DisplayFor(model => model.LastName)
</dd>

Após o último campo e imediatamente antes da marcação </dl> de fechamento, adicione o seguinte código para exibir uma lista de registros:After the last field and immediately before the closing </dl> tag, add the following code to display a list of enrollments:

<dt class="col-sm-2">
    @Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd class="col-sm-10">
    <table class="table">
        <tr>
            <th>Course Title</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Course.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
</dd>

Se o recuo do código estiver incorreto depois de colar o código, pressione CTRL-K-D para corrigi-lo.If code indentation is wrong after you paste the code, press CTRL-K-D to correct it.

Esse código percorre as entidades na propriedade de navegação Enrollments.This code loops through the entities in the Enrollments navigation property. Para cada registro, ele exibe o nome do curso e a nota.For each enrollment, it displays the course title and the grade. O título do curso é recuperado da entidade Course, que é armazenada na propriedade de navegação Course da entidade Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Execute o aplicativo, selecione a guia Alunos e clique no link Detalhes de um aluno.Run the app, select the Students tab, and click the Details link for a student. Você verá a lista de cursos e notas do aluno selecionado:You see the list of courses and grades for the selected student:

Página Detalhes do Aluno

Atualizar a página CriarUpdate the Create page

Em StudentsController.cs, modifique o método HttpPost Create adicionando um bloco try-catch e removendo a ID do atributo Bind.In StudentsController.cs, modify the HttpPost Create method by adding a try-catch block and removing ID from the Bind attribute.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
    [Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
    try
    {
        if (ModelState.IsValid)
        {
            _context.Add(student);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.
        ModelState.AddModelError("", "Unable to save changes. " +
            "Try again, and if the problem persists " +
            "see your system administrator.");
    }
    return View(student);
}

Esse código adiciona a entidade Student criada pelo associador de modelos do ASP.NET Core MVC ao conjunto de entidades Student e, em seguida, salva as alterações no banco de dados.This code adds the Student entity created by the ASP.NET Core MVC model binder to the Students entity set and then saves the changes to the database. (Associador de modelos refere-se à funcionalidade do ASP.NET Core MVC que facilita o trabalho com os dados enviados por um formulário. Um associador de modelos converte os valores de formulário postados em tipos CLR e passa-os para o método de ação em parâmetros.(Model binder refers to the ASP.NET Core MVC functionality that makes it easier for you to work with data submitted by a form; a model binder converts posted form values to CLR types and passes them to the action method in parameters. Nesse caso, o associador de modelos cria uma instância de uma entidade Student usando valores de propriedade da coleção Form.)In this case, the model binder instantiates a Student entity for you using property values from the Form collection.)

Você removeu ID do atributo Bind porque a ID é o valor de chave primária que o SQL Server definirá automaticamente quando a linha for inserida.You removed ID from the Bind attribute because ID is the primary key value which SQL Server will set automatically when the row is inserted. A entrada do usuário não define o valor da ID.Input from the user doesn't set the ID value.

Além do atributo Bind, o bloco try-catch é a única alteração que você fez no código gerado por scaffolding.Other than the Bind attribute, the try-catch block is the only change you've made to the scaffolded code. Se uma exceção que é derivada de DbUpdateException é capturada enquanto as alterações estão sendo salvas, uma mensagem de erro genérica é exibida.If an exception that derives from DbUpdateException is caught while the changes are being saved, a generic error message is displayed. Às vezes, as exceções DbUpdateException são causadas por algo externo ao aplicativo, em vez de por um erro de programação e, portanto, o usuário é aconselhado a tentar novamente.DbUpdateException exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Embora não implementado nesta amostra, um aplicativo de qualidade de produção registrará a exceção em log.Although not implemented in this sample, a production quality application would log the exception. Para obter mais informações, consulte a seção Log para informações em Monitoramento e telemetria (criando aplicativos de nuvem do mundo real com o Azure).For more information, see the Log for insight section in Monitoring and Telemetry (Building Real-World Cloud Apps with Azure).

O atributo ValidateAntiForgeryToken ajuda a impedir ataques CSRF (solicitação intersite forjada).The ValidateAntiForgeryToken attribute helps prevent cross-site request forgery (CSRF) attacks. O token é injetado automaticamente na exibição pelo FormTagHelper e é incluído quando o formulário é enviado pelo usuário.The token is automatically injected into the view by the FormTagHelper and is included when the form is submitted by the user. O token é validado pelo atributo ValidateAntiForgeryToken.The token is validated by the ValidateAntiForgeryToken attribute. Para obter mais informações sobre o CSRF, consulte Falsificação antissolicitação.For more information about CSRF, see Anti-Request Forgery.

Observação de segurança sobre o excesso de postagemSecurity note about overposting

O atributo Bind que o código gerado por scaffolding inclui no método Create é uma maneira de proteger contra o excesso de postagem em cenários de criação.The Bind attribute that the scaffolded code includes on the Create method is one way to protect against overposting in create scenarios. Por exemplo, suponha que a entidade Student inclua uma propriedade Secret que você não deseja que essa página da Web defina.For example, suppose the Student entity includes a Secret property that you don't want this web page to set.

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; }
}

Mesmo se você não tiver um campo Secret na página da Web, um invasor poderá usar uma ferramenta como o Fiddler ou escrever um JavaScript para postar um valor de formulário Secret.Even if you don't have a Secret field on the web page, a hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Sem o atributo Bind limitando os campos que o associador de modelos usa quando ele cria uma instância de Student, o associador de modelos seleciona esse valor de formulário Secret e usa-o para criar a instância da entidade Student.Without the Bind attribute limiting the fields that the model binder uses when it creates a Student instance, the model binder would pick up that Secret form value and use it to create the Student entity instance. Em seguida, seja qual for o valor que o invasor especificou para o campo de formulário Secret, ele é atualizado no banco de dados.Then whatever value the hacker specified for the Secret form field would be updated in your database. A imagem a seguir mostra a ferramenta Fiddler adicionando o campo Secret (com o valor "OverPost") aos valores de formulário postados.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler adicionando o campo Secreto

Em seguida, o valor "OverPost" é adicionado com êxito à propriedade Secret da linha inserida, embora você nunca desejou que a página da Web pudesse definir essa propriedade.The value "OverPost" would then be successfully added to the Secret property of the inserted row, although you never intended that the web page be able to set that property.

Impeça o excesso de postagem em cenários de edição lendo a entidade do banco de dados primeiro e, em seguida, chamando TryUpdateModel, passando uma lista explícita de propriedades permitidas.You can prevent overposting in edit scenarios by reading the entity from the database first and then calling TryUpdateModel, passing in an explicit allowed properties list. Esse é o método usado nestes tutoriais.That's the method used in these tutorials.

Uma maneira alternativa de impedir o excesso de postagem preferida por muitos desenvolvedores é usar modelos de exibição em vez de classes de entidade com o model binding.An alternative way to prevent overposting that's preferred by many developers is to use view models rather than entity classes with model binding. Inclua apenas as propriedades que você deseja atualizar no modelo de exibição.Include only the properties you want to update in the view model. Quando o associador de modelos MVC tiver concluído, copie as propriedades do modelo de exibição para a instância da entidade, opcionalmente usando uma ferramenta como o AutoMapper.Once the MVC model binder has finished, copy the view model properties to the entity instance, optionally using a tool such as AutoMapper. Use _context.Entry na instância de entidade para definir seu estado como Unchanged e, em seguida, defina Property("PropertyName").IsModified como verdadeiro em cada propriedade da entidade incluída no modelo de exibição.Use _context.Entry on the entity instance to set its state to Unchanged, and then set Property("PropertyName").IsModified to true on each entity property that's included in the view model. Esse método funciona nos cenários de edição e criação.This method works in both edit and create scenarios.

Testar a página CriarTest the Create page

O código em Views/Students/Create.cshtml usa os auxiliares de marcação label, input e span (para mensagens de validação) para cada campo.The code in Views/Students/Create.cshtml uses label, input, and span (for validation messages) tag helpers for each field.

Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em Criar Novo.Run the app, select the Students tab, and click Create New.

Insira nomes e uma data.Enter names and a date. Tente inserir uma data inválida se o navegador permitir fazer isso.Try entering an invalid date if your browser lets you do that. (Alguns navegadores forçam o uso de um seletor de data.) Em seguida, clique em Criar para ver a mensagem de erro.(Some browsers force you to use a date picker.) Then click Create to see the error message.

Erro de validação de data

Essa é a validação do lado do servidor que você obtém por padrão; em um tutorial posterior, você verá como adicionar atributos que gerarão o código para a validação do lado do cliente também.This is server-side validation that you get by default; in a later tutorial you'll see how to add attributes that will generate code for client-side validation also. O código realçado a seguir mostra a verificação de validação de modelo no método Create.The following highlighted code shows the model validation check in the Create method.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
    [Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
    try
    {
        if (ModelState.IsValid)
        {
            _context.Add(student);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.
        ModelState.AddModelError("", "Unable to save changes. " +
            "Try again, and if the problem persists " +
            "see your system administrator.");
    }
    return View(student);
}

Altere a data para um valor válido e clique em Criar para ver o novo aluno ser exibido na página Índice.Change the date to a valid value and click Create to see the new student appear in the Index page.

Atualizar a página EditarUpdate the Edit page

Em StudentController.cs, o método HttpGet Edit (aquele sem o atributo HttpPost) usa o método SingleOrDefaultAsync para recuperar a entidade Student selecionada, como você viu no método Details.In StudentController.cs, the HttpGet Edit method (the one without the HttpPost attribute) uses the SingleOrDefaultAsync method to retrieve the selected Student entity, as you saw in the Details method. Não é necessário alterar esse método.You don't need to change this method.

Substitua o método de ação HttpPost Edit pelo código a seguir.Replace the HttpPost Edit action method with the following code.

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
    if (id == null)
    {
        return NotFound();
    }
    var studentToUpdate = await _context.Students.FirstOrDefaultAsync(s => s.ID == id);
    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        try
        {
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
    }
    return View(studentToUpdate);
}

Essas alterações implementam uma melhor prática de segurança para evitar o excesso de postagem.These changes implement a security best practice to prevent overposting. O scaffolder gerou um atributo Bind e adicionou a entidade criada pelo associador de modelos ao conjunto de entidades com um sinalizador Modified.The scaffolder generated a Bind attribute and added the entity created by the model binder to the entity set with a Modified flag. Esse código não é recomendado para muitos cenários porque o atributo Bind limpa os dados pré-existentes nos campos não listados no parâmetro Include.That code isn't recommended for many scenarios because the Bind attribute clears out any pre-existing data in fields not listed in the Include parameter.

O novo código lê a entidade existente e chama TryUpdateModel para atualizar os campos na entidade recuperada com base na entrada do usuário nos dados de formulário postados.The new code reads the existing entity and calls TryUpdateModel to update fields in the retrieved entity based on user input in the posted form data. O controle automático de alterações do Entity Framework define o sinalizador Modified nos campos alterados pela entrada de formulário.The Entity Framework's automatic change tracking sets the Modified flag on the fields that are changed by form input. Quando o método SaveChanges é chamado, o Entity Framework cria instruções SQL para atualizar a linha de banco de dados.When the SaveChanges method is called, the Entity Framework creates SQL statements to update the database row. Os conflitos de simultaneidade são ignorados e somente as colunas de tabela que foram atualizadas pelo usuário são atualizadas no banco de dados.Concurrency conflicts are ignored, and only the table columns that were updated by the user are updated in the database. (Um tutorial posterior mostra como lidar com conflitos de simultaneidade.)(A later tutorial shows how to handle concurrency conflicts.)

Como uma melhor prática para evitar o excesso de postagem, os campos que você deseja que sejam atualizáveis pela página Editar estão na lista de permissões nos parâmetros TryUpdateModel.As a best practice to prevent overposting, the fields that you want to be updateable by the Edit page are whitelisted in the TryUpdateModel parameters. (A cadeia de caracteres vazia antes da lista de campos na lista de parâmetros destina-se ao uso de um prefixo com os nomes de campos de formulário.) Atualmente, não há nenhum campo extra que está sendo protegido, mas listar os campos que você deseja que o associador de modelos associe garante que, se você adicionar campos ao modelo de dados no futuro, eles serão automaticamente protegidos até que você adicione-os aqui de forma explícita.(The empty string preceding the list of fields in the parameter list is for a prefix to use with the form fields names.) Currently there are no extra fields that you're protecting, but listing the fields that you want the model binder to bind ensures that if you add fields to the data model in the future, they're automatically protected until you explicitly add them here.

Como resultado dessas alterações, a assinatura do método HttpPost Edit é a mesma do método HttpGet Edit; portanto, você já renomeou o método EditPost.As a result of these changes, the method signature of the HttpPost Edit method is the same as the HttpGet Edit method; therefore you've renamed the method EditPost.

Código HttpPost Edit alternativo: Criar e anexarAlternative HttpPost Edit code: Create and attach

O código de edição HttpPost recomendado garante que apenas as colunas alteradas sejam atualizadas e preserva os dados nas propriedades que você não deseja incluir para o model binding.The recommended HttpPost edit code ensures that only changed columns get updated and preserves data in properties that you don't want included for model binding. No entanto, a abordagem de primeira leitura exige uma leitura de banco de dados extra e pode resultar em um código mais complexo para lidar com conflitos de simultaneidade.However, the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts. Uma alternativa é anexar uma entidade criada pelo associador de modelos ao contexto do EF e marcá-la como modificada.An alternative is to attach an entity created by the model binder to the EF context and mark it as modified. (Não atualize o projeto com esse código; ele é mostrado somente para ilustrar uma abordagem opcional.)(Don't update your project with this code, it's only shown to illustrate an optional approach.)

public async Task<IActionResult> Edit(int id, [Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
    if (id != student.ID)
    {
        return NotFound();
    }
    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(student);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
    }
    return View(student);
}

Use essa abordagem quando a interface do usuário da página da Web incluir todos os campos na entidade e puder atualizar qualquer um deles.You can use this approach when the web page UI includes all of the fields in the entity and can update any of them.

O código gerado por scaffolding usa a abordagem "criar e anexar", mas apenas captura exceções DbUpdateConcurrencyException e retorna códigos de erro 404.The scaffolded code uses the create-and-attach approach but only catches DbUpdateConcurrencyException exceptions and returns 404 error codes. O exemplo mostrado captura qualquer exceção de atualização de banco de dados e exibe uma mensagem de erro.The example shown catches any database update exception and displays an error message.

Estados da entidadeEntity States

O contexto de banco de dados controla se as entidades em memória estão em sincronia com suas linhas correspondentes no banco de dados, e essas informações determinam o que acontece quando você chama o método SaveChanges.The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database, and this information determines what happens when you call the SaveChanges method. Por exemplo, quando você passa uma nova entidade para o método Add, o estado dessa entidade é definido como Added.For example, when you pass a new entity to the Add method, that entity's state is set to Added. Em seguida, quando você chama o método SaveChanges, o contexto de banco de dados emite um comando SQL INSERT.Then when you call the SaveChanges method, the database context issues a SQL INSERT command.

Uma entidade pode estar em um dos seguintes estados:An entity may be in one of the following states:

  • Added.Added. A entidade ainda não existe no banco de dados.The entity doesn't yet exist in the database. O método SaveChanges emite uma instrução INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged.Unchanged. Nada precisa ser feito com essa entidade pelo método SaveChanges.Nothing needs to be done with this entity by the SaveChanges method. Ao ler uma entidade do banco de dados, a entidade começa com esse status.When you read an entity from the database, the entity starts out with this status.

  • Modified.Modified. Alguns ou todos os valores de propriedade da entidade foram modificados.Some or all of the entity's property values have been modified. O método SaveChanges emite uma instrução UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted.Deleted. A entidade foi marcada para exclusão.The entity has been marked for deletion. O método SaveChanges emite uma instrução DELETE.The SaveChanges method issues a DELETE statement.

  • Detached.Detached. A entidade não está sendo controlada pelo contexto de banco de dados.The entity isn't being tracked by the database context.

Em um aplicativo da área de trabalho, em geral, as alterações de estado são definidas automaticamente.In a desktop application, state changes are typically set automatically. Você lê uma entidade e faz alterações em alguns de seus valores de propriedade.You read an entity and make changes to some of its property values. Isso faz com que seu estado da entidade seja alterado automaticamente para Modified.This causes its entity state to automatically be changed to Modified. Em seguida, quando você chama SaveChanges, o Entity Framework gera uma instrução SQL UPDATE que atualiza apenas as propriedades reais que você alterou.Then when you call SaveChanges, the Entity Framework generates a SQL UPDATE statement that updates only the actual properties that you changed.

Em um aplicativo Web, o DbContext que inicialmente lê uma entidade e exibe seus dados a serem editados é descartado depois que uma página é renderizada.In a web app, the DbContext that initially reads an entity and displays its data to be edited is disposed after a page is rendered. Quando o método de ação HttpPost Edit é chamado, é feita uma nova solicitação da Web e você tem uma nova instância do DbContext.When the HttpPost Edit action method is called, a new web request is made and you have a new instance of the DbContext. Se você ler novamente a entidade nesse novo contexto, simulará o processamento da área de trabalho.If you re-read the entity in that new context, you simulate desktop processing.

Mas se você não desejar fazer a operação de leitura extra, precisará usar o objeto de entidade criado pelo associador de modelos.But if you don't want to do the extra read operation, you have to use the entity object created by the model binder. A maneira mais simples de fazer isso é definir o estado da entidade como Modificado, como é feito no código HttpPost Edit alternativo mostrado anteriormente.The simplest way to do this is to set the entity state to Modified as is done in the alternative HttpPost Edit code shown earlier. Em seguida, quando você chama SaveChanges, o Entity Framework atualiza todas as colunas da linha de banco de dados, porque o contexto não tem como saber quais propriedades foram alteradas.Then when you call SaveChanges, the Entity Framework updates all columns of the database row, because the context has no way to know which properties you changed.

Caso deseje evitar a abordagem de primeira leitura, mas também deseje que a instrução SQL UPDATE atualize somente os campos que o usuário realmente alterar, o código será mais complexo.If you want to avoid the read-first approach, but you also want the SQL UPDATE statement to update only the fields that the user actually changed, the code is more complex. É necessário salvar os valores originais de alguma forma (por exemplo, usando campos ocultos) para que eles estejam disponíveis quando o método HttpPost Edit for chamado.You have to save the original values in some way (such as by using hidden fields) so that they're available when the HttpPost Edit method is called. Em seguida, você pode criar uma entidade Student usando os valores originais, chamar o método Attach com a versão original da entidade, atualizar os valores da entidade para os novos valores e, em seguida, chamar SaveChanges.Then you can create a Student entity using the original values, call the Attach method with that original version of the entity, update the entity's values to the new values, and then call SaveChanges.

Testar a página EditarTest the Edit page

Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em um hiperlink Editar.Run the app, select the Students tab, then click an Edit hyperlink.

Página Editar Alunos

Altere alguns dos dados e clique em Salvar.Change some of the data and click Save. A página Índice será aberta e você verá os dados alterados.The Index page opens and you see the changed data.

Atualizar a página ExcluirUpdate the Delete page

Em StudentController.cs, o código de modelo para o método HttpGet Delete usa o método SingleOrDefaultAsync para recuperar a entidade Student selecionada, como você viu nos métodos Details e Edit.In StudentController.cs, the template code for the HttpGet Delete method uses the SingleOrDefaultAsync method to retrieve the selected Student entity, as you saw in the Details and Edit methods. No entanto, para implementar uma mensagem de erro personalizada quando a chamada a SaveChanges falhar, você adicionará uma funcionalidade a esse método e à sua exibição correspondente.However, to implement a custom error message when the call to SaveChanges fails, you'll add some functionality to this method and its corresponding view.

Como você viu para operações de atualização e criação, as operações de exclusão exigem dois métodos de ação.As you saw for update and create operations, delete operations require two action methods. O método chamado em resposta a uma solicitação GET mostra uma exibição que dá ao usuário uma oportunidade de aprovar ou cancelar a operação de exclusão.The method that's called in response to a GET request displays a view that gives the user a chance to approve or cancel the delete operation. Se o usuário aprová-la, uma solicitação POST será criada.If the user approves it, a POST request is created. Quando isso acontece, o método HttpPost Delete é chamado e, em seguida, esse método executa, de fato, a operação de exclusão.When that happens, the HttpPost Delete method is called and then that method actually performs the delete operation.

Você adicionará um bloco try-catch ao método HttpPost Delete para tratar os erros que podem ocorrer quando o banco de dados é atualizado.You'll add a try-catch block to the HttpPost Delete method to handle any errors that might occur when the database is updated. Se ocorrer um erro, o método HttpPost Delete chamará o método HttpGet Delete, passando a ele um parâmetro que indica que ocorreu um erro.If an error occurs, the HttpPost Delete method calls the HttpGet Delete method, passing it a parameter that indicates that an error has occurred. Em seguida, o método HttpGet Delete exibe novamente a página de confirmação, junto com a mensagem de erro, dando ao usuário a oportunidade de cancelar ou tentar novamente.The HttpGet Delete method then redisplays the confirmation page along with the error message, giving the user an opportunity to cancel or try again.

Substitua o método de ação HttpGet Delete pelo código a seguir, que gerencia o relatório de erros.Replace the HttpGet Delete action method with the following code, which manages error reporting.

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

    var student = await _context.Students
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);
    if (student == null)
    {
        return NotFound();
    }

    if (saveChangesError.GetValueOrDefault())
    {
        ViewData["ErrorMessage"] =
            "Delete failed. Try again, and if the problem persists " +
            "see your system administrator.";
    }

    return View(student);
}

Este código aceita um parâmetro opcional que indica se o método foi chamado após uma falha ao salvar as alterações.This code accepts an optional parameter that indicates whether the method was called after a failure to save changes. Esse parâmetro é falso quando o método HttpGet Delete é chamado sem uma falha anterior.This parameter is false when the HttpGet Delete method is called without a previous failure. Quando ele é chamado pelo método HttpPost Delete em resposta a um erro de atualização de banco de dados, o parâmetro é verdadeiro, e uma mensagem de erro é passada para a exibição.When it's called by the HttpPost Delete method in response to a database update error, the parameter is true and an error message is passed to the view.

A abordagem de primeira leitura para HttpPost DeleteThe read-first approach to HttpPost Delete

Substitua o método de ação HttpPost Delete (chamado DeleteConfirmed) pelo código a seguir, que executa a operação de exclusão real e captura os erros de atualização de banco de dados.Replace the HttpPost Delete action method (named DeleteConfirmed) with the following code, which performs the actual delete operation and catches any database update errors.

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    var student = await _context.Students.FindAsync(id);
    if (student == null)
    {
        return RedirectToAction(nameof(Index));
    }

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

Esse código recupera a entidade selecionada e, em seguida, chama o método Remove para definir o status da entidade como Deleted.This code retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Quando SaveChanges é chamado, um comando SQL DELETE é gerado.When SaveChanges is called, a SQL DELETE command is generated.

A abordagem "criar e anexar" para HttpPost DeleteThe create-and-attach approach to HttpPost Delete

Se a melhoria do desempenho de um aplicativo de alto volume for uma prioridade, você poderá evitar uma consulta SQL desnecessária criando uma instância de uma entidade Student usando somente o valor de chave primária e, em seguida, definindo o estado da entidade como Deleted.If improving performance in a high-volume application is a priority, you could avoid an unnecessary SQL query by instantiating a Student entity using only the primary key value and then setting the entity state to Deleted. Isso é tudo o que o Entity Framework precisa para excluir a entidade.That's all that the Entity Framework needs in order to delete the entity. (Não coloque esse código no projeto; ele está aqui apenas para ilustrar uma alternativa.)(Don't put this code in your project; it's here just to illustrate an alternative.)

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    try
    {
        Student studentToDelete = new Student() { ID = id };
        _context.Entry(studentToDelete).State = EntityState.Deleted;
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    catch (DbUpdateException /* ex */)
    {
        //Log the error (uncomment ex variable name and write a log.)
        return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
    }
}

Se a entidade tiver dados relacionados, eles também deverão ser excluídos. Verifique se a exclusão em cascata está configurada no banco de dados.If the entity has related data that should also be deleted, make sure that cascade delete is configured in the database. Com essa abordagem para a exclusão de entidade, o EF talvez não perceba que há entidades relacionadas a serem excluídas.With this approach to entity deletion, EF might not realize there are related entities to be deleted.

Atualizar a exibição ExcluirUpdate the Delete view

Em Views/Student/Delete.cshtml, adicione uma mensagem de erro entre o cabeçalho h2 e o cabeçalho h3, conforme mostrado no seguinte exemplo:In Views/Student/Delete.cshtml, add an error message between the h2 heading and the h3 heading, as shown in the following example:

<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

Execute o aplicativo, selecione a guia Alunos e, em seguida, clique em um hiperlink Excluir:Run the app, select the Students tab, and click a Delete hyperlink:

Página Confirmação de exclusão

Clique em Excluir.Click Delete. A página Índice será exibida sem o aluno excluído.The Index page is displayed without the deleted student. (Você verá um exemplo de código de tratamento de erro em ação no tutorial sobre simultaneidade.)(You'll see an example of the error handling code in action in the concurrency tutorial.)

Fechará conexões de banco de dadosClose database connections

Para liberar os recursos contidos em uma conexão de banco de dados, a instância de contexto precisa ser descartada assim que possível quando você tiver terminado.To free up the resources that a database connection holds, the context instance must be disposed as soon as possible when you are done with it. A injeção de dependência interna do ASP.NET Core cuida dessa tarefa para você.The ASP.NET Core built-in dependency injection takes care of that task for you.

Em Startup.cs, chame o método de extensão AddDbContext para provisionar a classe DbContext no contêiner de DI do ASP.NET Core.In Startup.cs, you call the AddDbContext extension method to provision the DbContext class in the ASP.NET Core DI container. Esse método define o tempo de vida do serviço como Scoped por padrão.That method sets the service lifetime to Scoped by default. Scoped significa que o tempo de vida do objeto de contexto coincide com o tempo de vida da solicitação da Web, e o método Dispose será chamado automaticamente ao final da solicitação da Web.Scoped means the context object lifetime coincides with the web request life time, and the Dispose method will be called automatically at the end of the web request.

Lidar com transaçõesHandle transactions

Por padrão, o Entity Framework implementa transações de forma implícita.By default the Entity Framework implicitly implements transactions. Em cenários em que são feitas alterações em várias linhas ou tabelas e, em seguida, SaveChanges é chamado, o Entity Framework verifica automaticamente se todas as alterações tiveram êxito ou se falharam.In scenarios where you make changes to multiple rows or tables and then call SaveChanges, the Entity Framework automatically makes sure that either all of your changes succeed or they all fail. Se algumas alterações forem feitas pela primeira vez e, em seguida, ocorrer um erro, essas alterações serão revertidas automaticamente.If some changes are done first and then an error happens, those changes are automatically rolled back. Para cenários em que você precisa de mais controle – por exemplo, se desejar incluir operações feitas fora do Entity Framework em uma transação –, consulte Transações.For scenarios where you need more control -- for example, if you want to include operations done outside of Entity Framework in a transaction -- see Transactions.

Consultas sem controleNo-tracking queries

Quando um contexto de banco de dados recupera linhas de tabela e cria objetos de entidade que as representam, por padrão, ele controla se as entidades em memória estão em sincronia com o que está no banco de dados.When a database context retrieves table rows and creates entity objects that represent them, by default it keeps track of whether the entities in memory are in sync with what's in the database. Os dados em memória atuam como um cache e são usados quando uma entidade é atualizada.The data in memory acts as a cache and is used when you update an entity. Esse cache costuma ser desnecessário em um aplicativo Web porque as instâncias de contexto são normalmente de curta duração (uma nova é criada e descartada para cada solicitação) e o contexto que lê uma entidade normalmente é descartado antes que essa entidade seja usada novamente.This caching is often unnecessary in a web application because context instances are typically short-lived (a new one is created and disposed for each request) and the context that reads an entity is typically disposed before that entity is used again.

Desabilite o controle de objetos de entidade em memória chamando o método AsNoTracking.You can disable tracking of entity objects in memory by calling the AsNoTracking method. Os cenários típicos em que talvez você deseje fazer isso incluem os seguintes:Typical scenarios in which you might want to do that include the following:

  • Durante o tempo de vida do contexto, não é necessário atualizar entidades nem que o EF carregue automaticamente as propriedades de navegação com entidades recuperadas por consultas separadas.During the context lifetime you don't need to update any entities, and you don't need EF to automatically load navigation properties with entities retrieved by separate queries. Com frequência, essas condições são atendidas nos métodos de ação HttpGet de um controlador.Frequently these conditions are met in a controller's HttpGet action methods.

  • Você está executando uma consulta que recupera um volume grande de dados e apenas uma pequena parte dos dados retornados será atualizada.You are running a query that retrieves a large volume of data, and only a small portion of the returned data will be updated. Pode ser mais eficiente desativar o controle para a consulta grande e executar uma consulta posteriormente para as poucas entidades que precisam ser atualizadas.It may be more efficient to turn off tracking for the large query, and run a query later for the few entities that need to be updated.

  • Você deseja anexar uma entidade para atualizá-la, mas anteriormente, recuperou a mesma entidade para uma finalidade diferente.You want to attach an entity in order to update it, but earlier you retrieved the same entity for a different purpose. Como a entidade já está sendo controlada pelo contexto de banco de dados, não é possível anexar a entidade que você deseja alterar.Because the entity is already being tracked by the database context, you can't attach the entity that you want to change. Uma maneira de lidar com essa situação é chamar AsNoTracking na consulta anterior.One way to handle this situation is to call AsNoTracking on the earlier query.

Para obter mais informações, consulte Controle vs. Sem controle.For more information, see Tracking vs. No-Tracking.

Obter o códigoGet the code

Baixe ou exiba o aplicativo concluído.Download or view the completed application.

Próximas etapasNext steps

Neste tutorial, você:In this tutorial, you:

  • Personalizou a página DetalhesCustomized the Details page
  • Atualizou a página CriarUpdated the Create page
  • Atualizou a página EditarUpdated the Edit page
  • Atualizou a página ExcluirUpdated the Delete page
  • Fechou conexões de banco de dadosClosed database connections

Vá para o próximo tutorial para saber como expandir a funcionalidade da página Índice adicionando classificação, filtragem e paginação.Advance to the next tutorial to learn how to expand the functionality of the Index page by adding sorting, filtering, and paging.