Tutorial: Actualización de datos relacionados con EF en una aplicación ASP.NET MVC
En el tutorial anterior, ha mostrado datos relacionados. En este tutorial actualizará los datos relacionados. Para la mayoría de las relaciones, esto se puede hacer mediante la actualización de campos de clave externa o propiedades de navegación. En el caso de las relaciones de varios a varios, Entity Framework no expone la tabla de combinación directamente, por lo que se agregan y quitan entidades a las propiedades de navegación adecuadas, y desde ellas.
En las ilustraciones siguientes se muestran algunas de las páginas con las que va a trabajar.
En este tutorial ha:
- Personalización de las páginas de cursos
- Adición de una oficina a la página de instructores
- Adición de cursos a la página de instructores
- Actualización de DeleteConfirmed
- Agregar la ubicación de la oficina y cursos a la página Create
Requisitos previos
Personalización de las páginas de cursos
Cuando se crea una entidad de curso, debe tener una relación con un departamento existente. Para facilitar esto, el código con scaffolding incluye métodos de controlador y vistas de Create y Edit que incluyen una lista desplegable para seleccionar el departamento. La lista desplegable establece la propiedad de clave externa Course.DepartmentID
, y eso es todo lo que necesita Entity Framework para cargar la propiedad de navegación Department
con la entidad Department
adecuada. Podrá usar el código con scaffolding, pero cámbielo ligeramente para agregar el control de errores y ordenar la lista desplegable.
En CourseController.cs, elimine los cuatro métodos de Create
y Edit
, y reemplácelos por el código siguiente:
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
{
try
{
if (ModelState.IsValid)
{
db.Courses.Add(course);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Course course = db.Courses.Find(id);
if (course == null)
{
return HttpNotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var courseToUpdate = db.Courses.Find(id);
if (TryUpdateModel(courseToUpdate, "",
new string[] { "Title", "Credits", "DepartmentID" }))
{
try
{
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
var departmentsQuery = from d in db.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
}
Agregue la instrucción using
siguiente al principio del archivo:
using System.Data.Entity.Infrastructure;
El método PopulateDepartmentsDropDownList
obtiene una lista de todos los departamentos ordenados por nombre, crea una colección SelectList
para obtener una lista desplegable y pasa la colección a la vista en una propiedad ViewBag
. El método acepta el parámetro opcional selectedDepartment
, que permite al código que realiza la llamada especificar el elemento que se seleccionará cuando se procese la lista desplegable. La vista pasará el nombre DepartmentID
al asistente DropDownList y luego el asistente sabe que puede buscar en el objeto ViewBag
para una SelectList
denominada DepartmentID
.
El método Create
de HttpGet
llama al método PopulateDepartmentsDropDownList
sin configurar el elemento seleccionado, ya que el departamento todavía no está establecido para un nuevo curso:
public ActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
El método Edit
de HttpGet
establece el elemento seleccionado, basándose en el identificador del departamento que ya está asignado a la línea que se está editando:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Course course = db.Courses.Find(id);
if (course == null)
{
return HttpNotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
Los métodos HttpPost
para Create
y Edit
también incluyen código que configura el elemento seleccionado cuando vuelven a mostrar la página después de un error:
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
Este código garantiza que, cuando vuelve a aparecer la página para mostrar el mensaje de error, el departamento que se haya seleccionado permanece seleccionado.
Las vistas Course ya están con scaffolding con listas desplegables para el campo de departamento, pero no desea el título DepartmentID para este campo, por lo que debe modificar el siguiente cambio resaltado en el archivo Views\Course\Create.cshtml para cambiar el título.
@model ContosoUniversity.Models.Course
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Course</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.CourseID)
@Html.ValidationMessageFor(model => model.CourseID)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Credits)
@Html.ValidationMessageFor(model => model.Credits)
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="DepartmentID">Department</label>
<div class="col-md-10">
@Html.DropDownList("DepartmentID", String.Empty)
@Html.ValidationMessageFor(model => model.DepartmentID)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Haga el mismo cambio en Views\Course\Edit.cshtml.
Normalmente, el scaffolder no aplica scaffolding a una clave principal porque la base de datos genera el valor de clave y no se puede cambiar y no es un valor significativo que se mostrará a los usuarios. En el caso de las entidades Course, el scaffolder incluye un cuadro de texto para el campo CourseID
porque entiende que el atributo DatabaseGeneratedOption.None
significa que el usuario debe poder escribir el valor de clave principal. Pero no entiende que, dado que el número es significativo, quiere verlo en las otras vistas, por lo que debe agregarlo manualmente.
En Views\Course\Edit.cshtml, agregue un campo de número de curso antes del campo Title. Dado que el número de curso es la clave principal, esta se muestra, pero no se puede cambiar.
<div class="form-group">
@Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DisplayFor(model => model.CourseID)
</div>
</div>
Ya hay un campo oculto (asistente de Html.HiddenFor
) para el número de curso en la vista Edit. Agregar un asistente Html.LabelFor no elimina la necesidad de un campo oculto, porque no hace que el número de curso se incluya en los datos enviados cuando el usuario hace clic en Save en la página Edit.
En Views\Course\Delete.cshtml y Views\Course\Details.cshtml, cambie el título de nombre del departamento de "Name" a "Department" y agregue un campo de número de curso antes del campo Title.
<dt>
Department
</dt>
<dd>
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd>
@Html.DisplayFor(model => model.CourseID)
</dd>
Ejecute la página Create (muestre la página Course Index y haga clic en Create new) y escriba los datos de un nuevo curso:
Valor | Configuración |
---|---|
Número | Escriba 1000. |
Título | Escriba Algebra. |
Créditos | Escriba 4. |
department | Seleccione Mathematics. |
Haga clic en Crear. Se muestra la página Índice de cursos con el nuevo curso agregado a la lista. El nombre de departamento de la lista de páginas de índice proviene de la propiedad de navegación, que muestra que la relación se estableció correctamente.
Ejecute la página Editar (muestre la página Índice de cursos y haga clic en Editar en un curso).
Cambie los datos en la página y haga clic en Save. Se muestra la página Course Index con los datos del curso actualizados.
Adición de una oficina a la página de instructores
Al editar un registro de instructor, necesita poder actualizar la asignación de la oficina del instructor. La entidad Instructor
tiene una relación de uno a cero o uno con la entidad OfficeAssignment
, lo que significa que es necesario controlar las situaciones siguientes:
- Si el usuario borrase la asignación de oficina y originalmente tenía un valor, tendrá que quitar y eliminar la entidad
OfficeAssignment
. - Si el usuario escribiese un valor de asignación de oficina y originalmente estaba vacío, tendrá que crear una entidad
OfficeAssignment
. - Si el usuario cambiase el valor de una asignación de oficina, tendrá que cambiar el valor en una entidad
OfficeAssignment
existente.
Abra InstructorController.cs y examine el método HttpGet
Edit
:
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors.Find(id);
if (instructor == null)
{
return HttpNotFound();
}
ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
return View(instructor);
}
El código con scaffolding de aquí no es lo que quiere. Está configurando datos para una lista desplegable, pero lo que necesita es un cuadro de texto. Reemplace este método con el código siguiente:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (instructor == null)
{
return HttpNotFound();
}
return View(instructor);
}
Este código quita la instrucción ViewBag
y agrega carga diligente para la entidad OfficeAssignment
asociada. No es posible realizar la carga diligente con el método Find
, por lo que los métodos Where
y Single
se usan en su lugar para seleccionar el instructor.
Reemplace el método Edit
de HttpPost
con el código siguiente. que controla las actualizaciones de asignación de oficina:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
return View(instructorToUpdate);
}
La referencia a RetryLimitExceededException
requiere una instrucción using
; para agregarla, mantenga el cursor sobre RetryLimitExceededException
. Aparecerá el siguiente mensaje:
Seleccione Show potential fixes y, después, use System.Data.Entity.Infrastructure.
El código realiza lo siguiente:
Cambia el nombre del método a
EditPost
porque la firma ahora es la misma que el métodoHttpGet
(el atributoActionName
especifica que la dirección URL de /Edit/ aún está en uso).Obtiene la entidad
Instructor
actual de la base de datos mediante la carga diligente de la propiedad de navegaciónOfficeAssignment
. Esto es lo mismo que hizo en el métodoEdit
deHttpGet
.Actualiza la entidad
Instructor
recuperada con valores del enlazador de modelos. La sobrecarga TryUpdateModel usada le permite enumerar las propiedades que quiere incluir. Esto evita el registro excesivo, como se explica en el segundo tutorial.if (TryUpdateModel(instructorToUpdate, "", new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
Si la ubicación de la oficina está en blanco, establece la propiedad
Instructor.OfficeAssignment
en NULL para que se elimine la fila relacionada en la tablaOfficeAssignment
.if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location)) { instructorToUpdate.OfficeAssignment = null; }
Guarda los cambios en la base de datos.
En Views\Instructor\Edit.cshtml, después de los elementos div
del campo Fecha de contratación, agregue un nuevo campo para editar la ubicación de la oficina:
<div class="form-group">
@Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
</div>
Ejecute la página (seleccione la pestaña Instructores y, después, haga clic en Editar en un instructor). Cambie el valor de Office Location y haga clic en Save.
Adición de cursos a la página de instructores
Los instructores pueden impartir cualquier número de cursos. Ahora mejorará la página de edición de instructores al agregar la capacidad de cambiar las asignaciones de cursos mediante un grupo de casillas.
La relación entre las entidades Course
y Instructor
es de varios a varios, lo que significa que no tiene acceso directo a las propiedades de clave externa que se encuentran en la tabla de combinación. En su lugar, puede agregar y quitar entidades a la propiedad de navegación Instructor.Courses
y desde ella.
La interfaz de usuario que le permite cambiar los cursos a los que está asignado un instructor es un grupo de casillas. Se muestra una casilla para cada curso en la base de datos y se seleccionan aquellos a los que está asignado actualmente el instructor. El usuario puede activar o desactivar las casillas para cambiar las asignaciones de cursos. Si el número de cursos fuera mucho mayor, probablemente tendría que usar un método diferente de presentar los datos en la vista, pero usaría el mismo método de manipulación de propiedades de navegación para crear o eliminar relaciones.
Para proporcionar datos a la vista de la lista de casillas, deberá usar una clase de modelo de vista. Cree AssignedCourseData.cs en la carpeta ViewModels y reemplace el código existente con el código siguiente:
namespace ContosoUniversity.ViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
En InstructorController.cs, reemplace el método Edit
de HttpGet
por el siguiente código. Los cambios aparecen resaltados.
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.ID == id)
.Single();
if (instructor == null)
{
return HttpNotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = db.Courses;
var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewBag.Courses = viewModel;
}
El código agrega carga diligente para la propiedad de navegación Courses
y llama al método PopulateAssignedCourseData
nuevo para proporcionar información de la matriz de casilla mediante la clase de modelo de vista AssignedCourseData
.
El código en el método PopulateAssignedCourseData
lee todas las entidades Course
para cargar una lista de cursos mediante la clase de modelo de vista. Para cada curso, el código comprueba si existe el curso en la propiedad de navegación Courses
del instructor. Para crear una búsqueda eficaz al comprobar si un curso está asignado al instructor, los cursos asignados a este se colocan en una colección HashSet. La propiedad Assigned
está establecida en true
para aquellos cursos a los que esté asignado el instructor. La vista usará esta propiedad para determinar qué casilla debe mostrarse como seleccionada. Por último, la lista se pasa a la vista en una propiedad ViewBag
.
A continuación, agregue el código que se ejecuta cuando el usuario hace clic en Save. Reemplace el método EditPost
por el siguiente código, que llama a un nuevo método que actualiza la propiedad de navegación Courses
de la entidad Instructor
. Los cambios aparecen resaltados.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in db.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
}
}
La firma del método ahora es diferente del método Edit
de HttpGet
, por lo que el nombre del método cambia de EditPost
a Edit
.
Puesto que la vista no tiene una colección de entidades Course
, el enlazador de modelos no puede actualizar automáticamente la propiedad de navegación Courses
. En lugar de usar el enlazador de modelos para actualizar la propiedad de navegación Courses
, lo hará en el nuevo método UpdateInstructorCourses
. Por lo tanto, tendrá que excluir la propiedad Courses
del enlace de modelos. Esto no requiere ningún cambio en el código que llama a TryUpdateModel porque está usando la sobrecarga de listas explícita y Courses
no se encuentra en la lista de inclusión.
Si no se ha seleccionado ninguna casilla, el código de UpdateInstructorCourses
inicializa la propiedad de navegación Courses
con una colección vacía:
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
A continuación, el código recorre en bucle todos los cursos de la base de datos y coteja los que están asignados actualmente al instructor frente a los que se han seleccionado en la vista. Para facilitar las búsquedas eficaces, estas dos últimas colecciones se almacenan en objetos HashSet
.
Si se ha activado la casilla para un curso pero este no se encuentra en la propiedad de navegación Instructor.Courses
, el curso se agrega a la colección en la propiedad de navegación.
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
Si no se ha activado la casilla para un curso pero este se encuentra en la propiedad de navegación Instructor.Courses
, el curso se quita de la colección en la propiedad de navegación.
else
{
if (instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Remove(course);
}
}
En Views\Instructor\Edit.cshtml, agregue un campo Courses con una matriz de casillas al agregar el siguiente código inmediatamente después de los elementos div
del campo OfficeAssignment
y antes del elemento div
del botón Save:
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
Después de pegar el código, si los saltos de línea y la sangría no tienen el aspecto que hacen aquí, corrija manualmente todo para que parezca lo que ve aquí. No es necesario que la sangría sea perfecta, pero las líneas @</tr><tr>
, @:<td>
, @:</td>
y @</tr>
deben estar en una única línea tal y como se muestra, de lo contrario, obtendrá un error en tiempo de ejecución.
Este código crea una tabla HTML que tiene tres columnas. En cada columna hay una casilla seguida de un título que está formado por el número y el título del curso. Todas las casillas tienen el mismo nombre ("selectedCourses"), que informa al enlazador de modelos que se deben tratar como un grupo. El atributo value
de cada casilla se establece en el valor de CourseID.
. Cuando se publica la página, el enlazador de modelos pasa una matriz al controlador que consta de los valores CourseID
, solo para las casillas seleccionadas.
Cuando las casillas se representan inicialmente, aquellas que sean para cursos asignados al instructor tendrán atributos checked
, lo que las selecciona (las muestra activadas).
Después de cambiar las asignaciones de curso, querrá poder comprobar los cambios cuando el sitio vuelva a la página Index
. Por lo tanto, será necesario agregar una columna a la tabla de esa página. En este caso, no es necesario usar el objeto ViewBag
, ya que la información que quiere mostrar ya está en la propiedad de navegación Courses
de la entidad Instructor
que va a pasar a la página como modelo.
En Views\Instructor\Index.cshtml, agregue el título Cursos inmediatamente después del encabezado Oficina, tal y como se muestra en el ejemplo siguiente:
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
A continuación, agregue una nueva celda de detalle inmediatamente después de la celda de detalles de la ubicación de la oficina:
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
@Html.ActionLink("Select", "Index", new { id = item.ID }) |
@Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
@Html.ActionLink("Details", "Details", new { id = item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>
Ejecute la página Instructor Index para ver los cursos asignados a cada instructor.
Haga clic en Edit en un instructor para ver la página Edit.
Cambie algunas asignaciones de cursos y haga clic en Guardar. Los cambios que haga se reflejan en la página de índice.
Nota: El enfoque que se aplica aquí para modificar datos de los cursos del instructor funciona bien cuando hay un número limitado de cursos. Para las colecciones que son mucho más grandes, se necesitaría una interfaz de usuario y un método de actualización diferentes.
Actualización de DeleteConfirmed
En InstructorController.cs, elimine el método DeleteConfirmed
e inserte el código siguiente en su lugar.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Instructor instructor = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
db.Instructors.Remove(instructor);
var department = db.Departments
.Where(d => d.InstructorID == id)
.SingleOrDefault();
if (department != null)
{
department.InstructorID = null;
}
db.SaveChanges();
return RedirectToAction("Index");
}
Este código realiza los cambios siguientes:
- Si el instructor está asignado como administrador de cualquiera de los departamentos, quita la asignación de instructor de ese departamento. Sin este código, obtendrá un error de integridad referencial si intentó eliminar un instructor que se asignó como administrador para un departamento.
Este código no controla el escenario de un instructor asignado como administrador para varios departamentos. En el último tutorial, agregará código que evite que se produzca ese escenario.
Agregar la ubicación de la oficina y cursos a la página Create
En InstructorController.cs, elimine los métodos HttpGet
y HttpPost
de Create
, después, agregue el código siguiente en su lugar:
public ActionResult Create()
{
var instructor = new Instructor();
instructor.Courses = new List<Course>();
PopulateAssignedCourseData(instructor);
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.Courses = new List<Course>();
foreach (var course in selectedCourses)
{
var courseToAdd = db.Courses.Find(int.Parse(course));
instructor.Courses.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
db.Instructors.Add(instructor);
db.SaveChanges();
return RedirectToAction("Index");
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
Este código es similar a lo que ha visto para los métodos Edit, excepto que no hay cursos seleccionados inicialmente. El método Create
de HttpGet
no llama al método PopulateAssignedCourseData
porque pueda haber cursos seleccionados sino para proporcionar una colección vacía para el bucle foreach
en la vista (en caso contrario, el código de vista podría producir una excepción de referencia nula).
El método Create de HttpPost agrega cada curso seleccionado a la propiedad de navegación Courses antes de comprobar si hay errores de validación y agrega el instructor nuevo a la base de datos. Los cursos se agregan incluso si hay errores de modelo, por lo que cuando hay errores del modelo (por ejemplo, el usuario escribió una fecha no válida) y se vuelve a abrir la página con un mensaje de error, las selecciones de cursos que se habían realizado se restauran todas automáticamente.
Tenga en cuenta que, para poder agregar cursos a la propiedad de navegación Courses
, debe inicializar la propiedad como una colección vacía:
instructor.Courses = new List<Course>();
Como alternativa a hacerlo en el código de control, podría hacerlo en el modelo de Instructor cambiando el captador de propiedad para que cree automáticamente la colección si no existe, como se muestra en el ejemplo siguiente:
private ICollection<Course> _courses;
public virtual ICollection<Course> Courses
{
get
{
return _courses ?? (_courses = new List<Course>());
}
set
{
_courses = value;
}
}
Si modifica la propiedad Courses
de esta manera, puede quitar el código de inicialización de propiedad explícito del controlador.
En Views\Instructor\Create.cshtml, agregue un cuadro de texto de la ubicación de la oficina y casillas para cursos después del campo de fecha de contratación y antes del botón Submit.
<div class="form-group">
@Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.OfficeAssignment.Location)
@Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
Después de pegar el código, corrija los saltos de línea y la sangría como hizo anteriormente para la página Edit.
Ejecute la página Create y agregue un instructor.
Controlar transacciones
Como se explica en el tutorial sobre la funcionalidad CRUD básica, de forma predeterminada, Entity Framework implementa implícitamente las transacciones. Para escenarios donde se necesita más control, por ejemplo, si se quiere incluir operaciones realizadas fuera de Entity Framework en una transacción, consulte Trabajar con transacciones en MSDN.
Obtención del código
Descargar el proyecto completado
Recursos adicionales
Encontrará vínculos a otros recursos de Entity Framework en Acceso a datos de ASP.NET: Recursos recomendados.
Paso siguiente
En este tutorial ha:
- Personalizado páginas de cursos
- Agregado una oficina a la página de instructores
- Agregado cursos a la página de instructores
- Actualizado DeleteConfirmed
- Agregado la ubicación de la oficina y cursos a la página de creación
Pase al siguiente artículo para aprender a implementar un modelo de programación asincrónica.