Tutorial: Implementación de la funcionalidad CRUD: ASP.NET MVC con EF CoreTutorial: Implement CRUD Functionality - ASP.NET MVC with EF Core

En el tutorial anterior, creó una aplicación MVC que almacena y muestra los datos con Entity Framework y SQL Server LocalDB.In the previous tutorial, you created an MVC application that stores and displays data using the Entity Framework and SQL Server LocalDB. En este tutorial, podrá revisar y personalizar el código CRUD (crear, leer, actualizar y eliminar) que el scaffolding de MVC crea automáticamente para usted en controladores y vistas.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.

Nota

Es una práctica habitual implementar el modelo de repositorio con el fin de crear una capa de abstracción entre el controlador y la capa de acceso a datos.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 que estos tutoriales sean sencillos y se centren en enseñar a usar Entity Framework, no se usan repositorios.To keep these tutorials simple and focused on teaching how to use the Entity Framework itself, they don't use repositories. Para obtener información sobre los repositorios con EF, vea el último tutorial de esta serie.For information about repositories with EF, see the last tutorial in this series.

En este tutorial ha:In this tutorial, you:

  • Personalizar la página de detallesCustomize the Details page
  • Actualizar la página CreateUpdate the Create page
  • Actualizar la página EditUpdate the Edit page
  • Actualizar la página DeleteUpdate the Delete page
  • Cerrar conexiones de bases de datosClose database connections

Requisitos previosPrerequisites

Personalizar la página de detallesCustomize the Details page

En el código con scaffolding de la página Students Index se excluyó la propiedad Enrollments porque contiene una colección.The scaffolded code for the Students Index page left out the Enrollments property, because that property holds a collection. En la página Details, se mostrará el contenido de la colección en una tabla HTML.In the Details page, you'll display the contents of the collection in an HTML table.

En Controllers/StudentsController.cs, el método de acción para la vista Details usa el método SingleOrDefaultAsync para recuperar una única entidad Student.In Controllers/StudentsController.cs, the action method for the Details view uses the SingleOrDefaultAsync method to retrieve a single Student entity. Agregue código para llamar a los métodos Include,Add code that calls Include. ThenInclude y AsNoTracking, como se muestra en el siguiente código resaltado.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);
}

Los métodos Include y ThenInclude hacen que el contexto cargue la propiedad de navegación Student.Enrollments y, dentro de cada inscripción, la propiedad de navegación 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. Obtendrá más información sobre estos métodos en el tutorial de lectura de datos relacionados.You'll learn more about these methods in the read related data tutorial.

El método AsNoTracking mejora el rendimiento en casos en los que no se actualizarán las entidades devueltas en la duración del contexto actual.The AsNoTracking method improves performance in scenarios where the entities returned won't be updated in the current context's lifetime. Obtendrá más información sobre AsNoTracking al final de este tutorial.You'll learn more about AsNoTracking at the end of this tutorial.

Datos de rutaRoute data

El valor de clave que se pasa al método Details procede de los datos de ruta.The key value that's passed to the Details method comes from route data. Los datos de ruta son los que el enlazador de modelos encuentra en un segmento de la dirección URL.Route data is data that the model binder found in a segment of the URL. Por ejemplo, la ruta predeterminada especifica los segmentos de controlador, acción e identificador:For example, the default route specifies controller, action, and id segments:

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

En la dirección URL siguiente, la ruta predeterminada asigna Instructor como el controlador, Index como la acción y 1 como el identificador; estos son los valores de datos de ruta.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

La última parte de la dirección URL ("?courseID=2021") es un valor de cadena de consulta.The last part of the URL ("?courseID=2021") is a query string value. El enlazador de modelos también pasará el valor ID al parámetro id del método Index si se pasa como un valor de cadena 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

En la página Index, las instrucciones del asistente de etiquetas crean direcciones URL de hipervínculo en la vista de Razor.In the Index page, hyperlink URLs are created by tag helper statements in the Razor view. En el siguiente código de Razor, el parámetro id coincide con la ruta predeterminada, por lo que se agrega id a los datos de ruta.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>

Esto genera el siguiente código HTML cuando item.ID es 6:This generates the following HTML when item.ID is 6:

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

En el siguiente código de Razor, studentID no coincide con un parámetro en la ruta predeterminada, por lo que se agrega como una cadena 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>

Esto genera el siguiente código HTML cuando item.ID es 6:This generates the following HTML when item.ID is 6:

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

Para obtener más información sobre los asistentes de etiquetas, vea Asistentes de etiquetas en ASP.NET Core.For more information about tag helpers, see Asistentes de etiquetas en ASP.NET Core.

Agregar inscripciones a la vista de detallesAdd enrollments to the Details view

Abra Views/Students/Details.cshtml.Open Views/Students/Details.cshtml. Cada campo se muestra mediante los asistentes DisplayNameFor y DisplayFor, como se muestra en el ejemplo siguiente: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>

Después del último campo e inmediatamente antes de la etiqueta </dl> de cierre, agregue el código siguiente para mostrar una lista de las inscripciones: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>

Si la sangría de código no es correcta después de pegar el código, presione CTRL-K-D para corregirlo.If code indentation is wrong after you paste the code, press CTRL-K-D to correct it.

Este código recorre en bucle las entidades en la propiedad de navegación Enrollments.This code loops through the entities in the Enrollments navigation property. Para cada inscripción, se muestra el título del curso y la calificación.For each enrollment, it displays the course title and the grade. El título del curso se recupera de la entidad Course almacenada en la propiedad de navegación Course de la entidad Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Ejecute la aplicación, haga clic en la pestaña Students y después en el vínculo Details de un estudiante.Run the app, select the Students tab, and click the Details link for a student. Verá la lista de cursos y calificaciones para el alumno seleccionado:You see the list of courses and grades for the selected student:

Página de detalles del estudiante

Actualizar la página CreateUpdate the Create page

En StudentsController.cs, modifique el método HttpPost Create agregando un bloque try-catch y quitando ID del 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);
}

En este código se agrega la entidad Student creada por el enlazador de modelos de ASP.NET Core MVC al conjunto de entidades Students y después se guardan los cambios en la base de datos.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. (El enlazador de modelos se refiere a la funcionalidad de ASP.NET Core MVC que facilita trabajar con datos enviados por un formulario; un enlazador de modelos convierte los valores de formulario enviados en tipos CLR y los pasa al método de acción en 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. En este caso, el enlazador de modelos crea instancias de una entidad Student mediante valores de propiedad de la colección Form).In this case, the model binder instantiates a Student entity for you using property values from the Form collection.)

Se ha quitado ID del atributo Bind porque ID es el valor de clave principal que SQL Server establecerá automáticamente cuando se inserte la fila.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. La entrada del usuario no establece el valor ID.Input from the user doesn't set the ID value.

Aparte del atributo Bind, el bloque try-catch es el único cambio que se ha realizado en el código con scaffolding.Other than the Bind attribute, the try-catch block is the only change you've made to the scaffolded code. Si se detecta una excepción derivada de DbUpdateException mientras se guardan los cambios, se muestra un mensaje de error genérico.If an exception that derives from DbUpdateException is caught while the changes are being saved, a generic error message is displayed. En ocasiones, las excepciones DbUpdateException se deben a algo externo a la aplicación y no a un error de programación, por lo que se recomienda al usuario que vuelva a intentarlo.DbUpdateException exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Aunque no se ha implementado en este ejemplo, en una aplicación de producción de calidad se debería registrar la excepción.Although not implemented in this sample, a production quality application would log the exception. Para obtener más información, vea la sección Registro para obtener información de Supervisión y telemetría (creación de aplicaciones de nube reales con Azure).For more information, see the Log for insight section in Monitoring and Telemetry (Building Real-World Cloud Apps with Azure).

El atributo ValidateAntiForgeryToken ayuda a evitar ataques de falsificación de solicitud entre sitios (CSRF).The ValidateAntiForgeryToken attribute helps prevent cross-site request forgery (CSRF) attacks. El token se inserta automáticamente en la vista por medio de FormTagHelper y se incluye cuando el usuario envía el formulario.The token is automatically injected into the view by the FormTagHelper and is included when the form is submitted by the user. El token se valida mediante el atributo ValidateAntiForgeryToken.The token is validated by the ValidateAntiForgeryToken attribute. Para obtener más información sobre CSRF, vea Prevención de ataques de falsificación de solicitud.For more information about CSRF, see Anti-Request Forgery.

Nota de seguridad sobre la publicación excesivaSecurity note about overposting

El atributo Bind que el código con scaffolding incluye en el método Create es una manera de protegerse contra la publicación excesiva en escenarios de creación.The Bind attribute that the scaffolded code includes on the Create method is one way to protect against overposting in create scenarios. Por ejemplo, suponga que la entidad Student incluye una propiedad Secret que no quiere que esta página web establezca.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; }
}

Aunque no tenga un campo Secret en la página web, un hacker podría usar una herramienta como Fiddler, o bien escribir código de JavaScript, para enviar un valor de formulario 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. Sin el atributo Bind para limitar los campos que el enlazador de modelos usa cuando crea una instancia Student, el enlazador de modelos seleccionaría ese valor de formulario Secret y lo usaría para crear la instancia de la entidad 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. Después, el valor que el hacker haya especificado para el campo de formulario Secret se actualizaría en la base de datos.Then whatever value the hacker specified for the Secret form field would be updated in your database. En la imagen siguiente se muestra cómo la herramienta Fiddler agrega el campo Secret (con el valor "OverPost") a los valores de formulario enviados.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Campo Secret agregado por Fiddler

Después, el valor "OverPost" se agregaría correctamente a la propiedad Secret de la fila insertada, aunque no hubiera previsto que la página web pudiera establecer esa propiedad.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.

Puede evitar la publicación excesiva en escenarios de edición si primero lee la entidad desde la base de datos y después llama a TryUpdateModel, pasando una lista de propiedades permitidas de manera explícita.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. Es el método que se usa en estos tutoriales.That's the method used in these tutorials.

Una manera alternativa de evitar la publicación excesiva que muchos desarrolladores prefieren consiste en usar modelos de vista en lugar de clases de entidad con el enlace de modelos.An alternative way to prevent overposting that's preferred by many developers is to use view models rather than entity classes with model binding. Incluya en el modelo de vista solo las propiedades que quiera actualizar.Include only the properties you want to update in the view model. Una vez que haya finalizado el enlazador de modelos de MVC, copie las propiedades del modelo de vista a la instancia de entidad, opcionalmente con una herramienta como 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 en la instancia de entidad para establecer su estado en Unchanged y, después, establezca Property("PropertyName").IsModified en true en todas las propiedades de entidad que se incluyan en el modelo de vista.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. Este método funciona tanto en escenarios de edición como de creación.This method works in both edit and create scenarios.

Probar la página CreateTest the Create page

En el código de Views/Students/Create.cshtml se usan los asistentes de etiquetas label, input y span (para los mensajes de validación) en cada campo.The code in Views/Students/Create.cshtml uses label, input, and span (for validation messages) tag helpers for each field.

Ejecute la aplicación, haga clic en la pestaña Students y después en Create New.Run the app, select the Students tab, and click Create New.

Escriba los nombres y una fecha.Enter names and a date. Pruebe a escribir una fecha no válida si el explorador se lo permite.Try entering an invalid date if your browser lets you do that. (Algunos exploradores le obligan a usar un selector de fecha). Después, haga clic en Crear para ver el mensaje de error.(Some browsers force you to use a date picker.) Then click Create to see the error message.

Error de validación de fecha

Es la validación del lado servidor que obtendrá de forma predeterminada; en un tutorial posterior verá cómo agregar atributos que también generan código para la validación del lado cliente.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. En el siguiente código resaltado se muestra la comprobación de validación del modelo en el 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);
}

Cambie la fecha por un valor válido y haga clic en Crear para ver el alumno nuevo en la página Index.Change the date to a valid value and click Create to see the new student appear in the Index page.

Actualizar la página EditUpdate the Edit page

En StudentController.cs, el método HttpGet Edit (el que no tiene el atributo HttpPost) usa el método SingleOrDefaultAsync para recuperar la entidad Student seleccionada, como se vio en el 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. No es necesario cambiar este método.You don't need to change this method.

Reemplace el método de acción HttpPost Edit con el código siguiente.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);
}

Estos cambios implementan un procedimiento recomendado de seguridad para evitar la publicación excesiva.These changes implement a security best practice to prevent overposting. El proveedor de scaffolding generó un atributo Bind y agregó la entidad creada por el enlazador de modelos a la entidad establecida con una marca Modified.The scaffolder generated a Bind attribute and added the entity created by the model binder to the entity set with a Modified flag. Ese código no se recomienda para muchos escenarios porque el atributo Bind borra los datos ya existentes en los campos que no se enumeran en el 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.

El código nuevo lee la entidad existente y llama a TryUpdateModel para actualizar los campos en la entidad recuperada en función de la entrada del usuario en los datos de formulario publicados.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. El seguimiento de cambios automático de Entity Framework establece la marca Modified en los campos que se cambian mediante la entrada de formulario.The Entity Framework's automatic change tracking sets the Modified flag on the fields that are changed by form input. Cuando se llama al método SaveChanges, Entity Framework crea instrucciones SQL para actualizar la fila de la base de datos.When the SaveChanges method is called, the Entity Framework creates SQL statements to update the database row. Los conflictos de simultaneidad se ignoran y las columnas de tabla que se actualizaron por el usuario se actualizan en la base de datos.Concurrency conflicts are ignored, and only the table columns that were updated by the user are updated in the database. (En un tutorial posterior se muestra cómo controlar los conflictos de simultaneidad).(A later tutorial shows how to handle concurrency conflicts.)

Como procedimiento recomendado para evitar la publicación excesiva, los campos que quiera que se puedan actualizar por la página Edit se incluyen en la lista de permitidos en los 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. (La cadena vacía que precede a la lista de campos en la lista de parámetros es para el prefijo que se usa con los nombres de campos de formulario). Actualmente no se está protegiendo ningún campo adicional, pero enumerar los campos que quiere que el enlazador de modelos enlace garantiza que, si en el futuro agrega campos al modelo de datos, se protejan automáticamente hasta que los agregue aquí 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 de estos cambios, la firma de método del método HttpPost Edit es la misma que la del método HttpGet Edit; por tanto, se ha cambiado el nombre del 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 alternativo para HttpPost Edit: crear y adjuntarAlternative HttpPost Edit code: Create and attach

El código recomendado para HttpPost Edit garantiza que solo se actualicen las columnas cambiadas y conserva los datos de las propiedades que no quiere que se incluyan para el enlace de modelos.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. Pero el enfoque de primera lectura requiere una operación de lectura adicional de la base de datos y puede dar lugar a código más complejo para controlar los conflictos de simultaneidad.However, the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts. Una alternativa consiste en adjuntar una entidad creada por el enlazador de modelos en el contexto de EF y marcarla como modificada.An alternative is to attach an entity created by the model binder to the EF context and mark it as modified. (No actualice el proyecto con este código, solo se muestra para ilustrar un enfoque 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);
}

Puede usar este enfoque cuando la interfaz de usuario de la página web incluya todos los campos de la entidad y puede actualizar cualquiera de ellos.You can use this approach when the web page UI includes all of the fields in the entity and can update any of them.

En el código con scaffolding se usa el enfoque de crear y adjuntar, pero solo se detectan las excepciones DbUpdateConcurrencyException y se devuelven códigos de error 404.The scaffolded code uses the create-and-attach approach but only catches DbUpdateConcurrencyException exceptions and returns 404 error codes. En el ejemplo mostrado se detecta cualquier excepción de actualización de base de datos y se muestra un mensaje de error.The example shown catches any database update exception and displays an error message.

Estados de entidadEntity States

El contexto de la base de datos realiza el seguimiento de si las entidades en memoria están sincronizadas con sus filas correspondientes en la base de datos, y esta información determina lo que ocurre cuando se llama al 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 ejemplo, cuando se pasa una nueva entidad al método Add, el estado de esa entidad se establece en Added.For example, when you pass a new entity to the Add method, that entity's state is set to Added. Después, cuando se llama al método SaveChanges, el contexto de la base de datos emite un comando INSERT de SQL.Then when you call the SaveChanges method, the database context issues a SQL INSERT command.

Una entidad puede estar en uno de los estados siguientes:An entity may be in one of the following states:

  • Added.Added. La entidad no existe todavía en la base de datos.The entity doesn't yet exist in the database. El método SaveChanges emite una instrucción INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged.Unchanged. No es necesario hacer nada con esta entidad mediante el método SaveChanges.Nothing needs to be done with this entity by the SaveChanges method. Al leer una entidad de la base de datos, la entidad empieza con este estado.When you read an entity from the database, the entity starts out with this status.

  • Modified.Modified. Se han modificado algunos o todos los valores de propiedad de la entidad.Some or all of the entity's property values have been modified. El método SaveChanges emite una instrucción UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted.Deleted. La entidad se ha marcado para su eliminación.The entity has been marked for deletion. El método SaveChanges emite una instrucción DELETE.The SaveChanges method issues a DELETE statement.

  • Detached.Detached. El contexto de base de datos no está realizando el seguimiento de la entidad.The entity isn't being tracked by the database context.

En una aplicación de escritorio, los cambios de estado normalmente se establecen de forma automática.In a desktop application, state changes are typically set automatically. Lea una entidad y realice cambios en algunos de sus valores de propiedad.You read an entity and make changes to some of its property values. Esto hace que su estado de entidad cambie automáticamente a Modified.This causes its entity state to automatically be changed to Modified. Después, cuando se llama a SaveChanges, Entity Framework genera una instrucción UPDATE de SQL que solo actualiza las propiedades reales que se hayan cambiado.Then when you call SaveChanges, the Entity Framework generates a SQL UPDATE statement that updates only the actual properties that you changed.

En una aplicación web, el DbContext que inicialmente lee una entidad y muestra sus datos para que se puedan modificar se elimina después de representar una página.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. Cuando se llama al método de acción HttpPost Edit, se realiza una nueva solicitud web y se obtiene una nueva instancia de DbContext.When the HttpPost Edit action method is called, a new web request is made and you have a new instance of the DbContext. Si vuelve a leer la entidad en ese contexto nuevo, simulará el procesamiento de escritorio.If you re-read the entity in that new context, you simulate desktop processing.

Pero si no quiere realizar la operación de lectura adicional, tendrá que usar el objeto de entidad creado por el enlazador 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. La manera más sencilla de hacerlo consiste en establecer el estado de la entidad en Modified tal y como se hace en el 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. Después, cuando se llama a SaveChanges, Entity Framework actualiza todas las columnas de la fila de la base de datos, porque el contexto no tiene ninguna manera de saber qué propiedades se han cambiado.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.

Si quiere evitar el enfoque de primera lectura pero también que la instrucción UPDATE de SQL actualice solo los campos que el usuario ha cambiado realmente, el código es más complejo.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. Debe guardar los valores originales de alguna manera (por ejemplo mediante campos ocultos) para que estén disponibles cuando se llame al método HttpPost Edit.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. Después puede crear una entidad Student con los valores originales, llamar al método Attach con esa versión original de la entidad, actualizar los valores de la entidad con los valores nuevos y luego llamar a 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.

Probar la página EditTest the Edit page

Ejecute la aplicación, haga clic en la pestaña Students y después en un hipervínculo Edit.Run the app, select the Students tab, then click an Edit hyperlink.

Página de edición de estudiantes

Cambie algunos de los datos y haga clic en Guardar.Change some of the data and click Save. Se abrirá la página Index y verá los datos modificados.The Index page opens and you see the changed data.

Actualizar la página DeleteUpdate the Delete page

En StudentController.cs, el código de plantilla para el método HttpGet Delete usa el método SingleOrDefaultAsync para recuperar la entidad Student seleccionada, como se vio en los métodos Details y 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. Pero para implementar un mensaje de error personalizado cuando se produce un error en la llamada a SaveChanges, agregará funcionalidad a este método y su vista correspondiente.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 se vio para las operaciones de actualización y creación, las operaciones de eliminación requieren dos métodos de acción.As you saw for update and create operations, delete operations require two action methods. El método que se llama en respuesta a una solicitud GET muestra una vista que proporciona al usuario la oportunidad de aprobar o cancelar la operación de eliminación.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. Si el usuario la aprueba, se crea una solicitud POST.If the user approves it, a POST request is created. Cuando esto ocurre, se llama al método HttpPost Delete y, después, ese método es el que realiza la operación de eliminación.When that happens, the HttpPost Delete method is called and then that method actually performs the delete operation.

Agregará un bloque try-catch al método HttpPost Delete para controlar los errores que puedan producirse cuando se actualice la base de datos.You'll add a try-catch block to the HttpPost Delete method to handle any errors that might occur when the database is updated. Si se produce un error, el método HttpPost Delete llama al método HttpGet Delete, pasando un parámetro que indica que se ha producido un error.If an error occurs, the HttpPost Delete method calls the HttpGet Delete method, passing it a parameter that indicates that an error has occurred. Después, el método HttpGet Delete vuelve a mostrar la página de confirmación junto con el mensaje de error, dando al usuario la oportunidad de cancelar o volver a intentarlo.The HttpGet Delete method then redisplays the confirmation page along with the error message, giving the user an opportunity to cancel or try again.

Reemplace el método de acción HttpGet Delete con el código siguiente, que administra los informes de errores.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 acepta un parámetro opcional que indica si se llamó al método después de un error al guardar los cambios.This code accepts an optional parameter that indicates whether the method was called after a failure to save changes. Este parámetro es false cuando se llama al método HttpGet Delete sin un error anterior.This parameter is false when the HttpGet Delete method is called without a previous failure. Cuando se llama por medio del método HttpPost Delete en respuesta a un error de actualización de base de datos, el parámetro es true y se pasa un mensaje de error a la vista.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.

El enfoque de primera lectura para HttpPost DeleteThe read-first approach to HttpPost Delete

Reemplace el método de acción HttpPost Delete (denominado DeleteConfirmed) con el código siguiente, que realiza la operación de eliminación y captura los errores de actualización de base de datos.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 });
    }
}

Este código recupera la entidad seleccionada y después llama al método Remove para establecer el estado de la entidad en Deleted.This code retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Cuando se llama a SaveChanges, se genera un comando DELETE de SQL.When SaveChanges is called, a SQL DELETE command is generated.

El enfoque de crear y adjuntar para HttpPost DeleteThe create-and-attach approach to HttpPost Delete

Si mejorar el rendimiento de una aplicación de gran volumen es una prioridad, podría evitar una consulta SQL innecesaria creando instancias de una entidad Student solo con el valor de clave principal y después estableciendo el estado de la entidad en 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. Eso es todo lo que necesita Entity Framework para eliminar la entidad.That's all that the Entity Framework needs in order to delete the entity. (No incluya este código en el proyecto; únicamente se muestra para ilustrar una 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 });
    }
}

Si la entidad tiene datos relacionados que también se deban eliminar, asegúrese de configurar la eliminación en cascada en la base de datos.If the entity has related data that should also be deleted, make sure that cascade delete is configured in the database. Con este enfoque de eliminación de entidades, es posible que EF no sepa que hay entidades relacionadas para eliminar.With this approach to entity deletion, EF might not realize there are related entities to be deleted.

Actualizar la vista DeleteUpdate the Delete view

En Views/Student/Delete.cshtml, agregue un mensaje de error entre los títulos h2 y h3, como se muestra en el ejemplo siguiente: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>

Ejecute la aplicación, haga clic en la pestaña Students y después en un hipervínculo Delete:Run the app, select the Students tab, and click a Delete hyperlink:

Página de confirmación de la eliminación

Haga clic en Eliminar.Click Delete. Se mostrará la página de índice sin el estudiante eliminado.The Index page is displayed without the deleted student. (Verá un ejemplo del código de control de errores en funcionamiento en el tutorial sobre la simultaneidad).(You'll see an example of the error handling code in action in the concurrency tutorial.)

Cerrar conexiones de bases de datosClose database connections

Para liberar los recursos que contiene una conexión de base de datos, la instancia de contexto debe eliminarse tan pronto como sea posible cuando haya terminado con ella.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. La inserción de dependencias integrada de ASP.NET Core se encarga de esa tarea.The ASP.NET Core built-in dependency injection takes care of that task for you.

En Startup.cs, se llama al método de extensión AddDbContext para aprovisionar la clase DbContext en el contenedor de inserción de dependencias de ASP.NET Core.In Startup.cs, you call the AddDbContext extension method to provision the DbContext class in the ASP.NET Core DI container. Ese método establece la duración del servicio en Scoped de forma predeterminada.That method sets the service lifetime to Scoped by default. Scoped significa que la duración del objeto de contexto coincide con la duración de la solicitud web, y el método Dispose se llamará automáticamente al final de la solicitud 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.

Controlar transaccionesHandle transactions

De forma predeterminada, Entity Framework implementa las transacciones de manera implícita.By default the Entity Framework implicitly implements transactions. En escenarios donde se realizan cambios en varias filas o tablas, y después se llama a SaveChanges, Entity Framework se asegura automáticamente de que todos los cambios se realicen correctamente o se produzca un error en todos ellos.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. Si primero se realizan algunos cambios y después se produce un error, los cambios se revierten automáticamente.If some changes are done first and then an error happens, those changes are automatically rolled back. Para escenarios donde se necesita más control, por ejemplo, si se quieren incluir operaciones realizadas fuera de Entity Framework en una transacción, vea Transacciones.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 de no seguimientoNo-tracking queries

Cuando un contexto de base de datos recupera las filas de tabla y crea objetos de entidad que las representa, de forma predeterminada realiza el seguimiento de si las entidades en memoria están sincronizadas con el contenido de la base de datos.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. Los datos en memoria actúan como una caché y se usan cuando se actualiza una entidad.The data in memory acts as a cache and is used when you update an entity. Este almacenamiento en caché suele ser necesario en una aplicación web porque las instancias de contexto normalmente son de corta duración (para cada solicitud se crea una y se elimina) y el contexto que lee una entidad normalmente se elimina antes de volver a usar esa entidad.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.

Puede deshabilitar el seguimiento de los objetos de entidad en memoria mediante una llamada al método AsNoTracking.You can disable tracking of entity objects in memory by calling the AsNoTracking method. Los siguientes son escenarios típicos en los que es posible que quiera hacer esto:Typical scenarios in which you might want to do that include the following:

  • Durante la vigencia del contexto no es necesario actualizar ninguna entidad ni que EF cargue automáticamente las propiedades de navegación con las entidades recuperadas por consultas independientes.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. Estas condiciones se cumplen frecuentemente en los métodos de acción HttpGet del controlador.Frequently these conditions are met in a controller's HttpGet action methods.

  • Se ejecuta una consulta que recupera un gran volumen de datos y solo se actualiza una pequeña parte de los datos devueltos.You are running a query that retrieves a large volume of data, and only a small portion of the returned data will be updated. Puede ser más eficaz desactivar el seguimiento de la consulta de gran tamaño y ejecutar una consulta más adelante para las pocas entidades que deban actualizarse.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.

  • Se quiere adjuntar una entidad para actualizarla, pero antes se recuperó la misma entidad para un propósito diferente.You want to attach an entity in order to update it, but earlier you retrieved the same entity for a different purpose. Como el contexto de base de datos ya está realizando el seguimiento de la entidad, no se puede adjuntar la entidad que se quiere cambiar.Because the entity is already being tracked by the database context, you can't attach the entity that you want to change. Una manera de controlar esta situación consiste en llamar a AsNoTracking en la consulta anterior.One way to handle this situation is to call AsNoTracking on the earlier query.

Para obtener más información, vea Tracking vs. No-Tracking (Diferencia entre consultas de seguimiento y no seguimiento).For more information, see Tracking vs. No-Tracking.

Obtención del códigoGet the code

Descargue o vea la aplicación completa.Download or view the completed application.

Pasos siguientesNext steps

En este tutorial ha:In this tutorial, you:

  • Personalizado la página de detallesCustomized the Details page
  • Actualizado la página CreateUpdated the Create page
  • Actualizado la página EditUpdated the Edit page
  • Actualizado la página DeleteUpdated the Delete page
  • Cerrado conexiones de bases de datosClosed database connections

Pase al tutorial siguiente para obtener información sobre cómo expandir la funcionalidad de la página Index mediante la adición de ordenación, filtrado y paginación.Advance to the next tutorial to learn how to expand the functionality of the Index page by adding sorting, filtering, and paging.