Condividi tramite


Esercitazione: Aggiornare i dati correlati con Entity Framework in un'app MVC ASP.NET

Nell'esercitazione precedente sono stati visualizzati i dati correlati. In questa esercitazione verranno aggiornati i dati correlati. Per la maggior parte delle relazioni, questa operazione può essere eseguita aggiornando campi chiave esterna o proprietà di navigazione. Per le relazioni molti-a-molti, Entity Framework non espone direttamente la tabella join, quindi è possibile aggiungere e rimuovere entità da e verso le proprietà di navigazione appropriate.

Le figure seguenti illustrano alcune delle pagine che verranno usate.

Course_create_page

Instructor_edit_page_with_courses

Modifica dell'insegnante con i corsi

In questa esercitazione:

  • Personalizzare le pagine dei corsi
  • Pagina Aggiungi ufficio agli insegnanti
  • Aggiungere corsi alla pagina degli insegnanti
  • Aggiorna eliminazione confermata
  • Aggiungere posizione dell'ufficio e corsi alla pagina Create (Crea)

Prerequisiti

Personalizzare le pagine dei corsi

Quando viene creata, una nuova entità corso deve essere in relazione con un dipartimento esistente. Per semplificare il raggiungimento di questo obiettivo, il codice con scaffolding include i metodi del controller e le visualizzazioni di creazione e modifica includono un elenco a discesa per la selezione del dipartimento. L'elenco a discesa imposta la Course.DepartmentID proprietà di chiave esterna ed è tutto ciò che è necessario per caricare la Department proprietà di navigazione con l'entità appropriata Department . Verrà usato il codice con scaffolding, che però verrà modificato leggermente per aggiungere la gestione degli errori e l'ordinamento dell'elenco a discesa.

In CourseController.cs eliminare i quattro Create metodi e Edit e sostituirli con il codice seguente:

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

Aggiungere l'istruzione seguente using all'inizio del file:

using System.Data.Entity.Infrastructure;

Il PopulateDepartmentsDropDownList metodo ottiene un elenco di tutti i reparti ordinati in base al nome, crea una SelectList raccolta per un elenco a discesa e passa la raccolta alla visualizzazione in una ViewBag proprietà. Il metodo accetta il parametro facoltativo selectedDepartment, che consente al codice chiamante di specificare l'elemento che deve essere selezionato quando viene eseguito il rendering dell'elenco a discesa. La vista passerà il nome DepartmentID all'helper DropDownList e l'helper sa quindi di cercare nell'oggetto ViewBag un SelectList oggetto denominato DepartmentID.

Il HttpGet Create metodo chiama il PopulateDepartmentsDropDownList metodo senza impostare l'elemento selezionato, perché per un nuovo corso il reparto non è ancora stato stabilito:

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

Il HttpGet Edit metodo imposta l'elemento selezionato, in base all'ID del reparto già assegnato al corso da modificare:

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

I HttpPost metodi per entrambi Create e Edit includono anche il codice che imposta l'elemento selezionato quando redisplayno la pagina dopo un errore:

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

Questo codice garantisce che quando la pagina viene rieseguita per visualizzare il messaggio di errore, il reparto selezionato rimane selezionato.

Le visualizzazioni Course sono già state scaffolding con elenchi a discesa per il campo reparto, ma non si vuole che la didascalia DepartmentID per questo campo sia evidenziata, quindi apportare la modifica evidenziata seguente al file Views\Course\Create.cshtml per modificare la didascalia.

@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")
}

Apportare la stessa modifica in Views\Course\Edit.cshtml.

In genere lo scaffolder non esegue lo scaffolding di una chiave primaria perché il valore della chiave viene generato dal database e non può essere modificato e non è un valore significativo da visualizzare agli utenti. Per le entità Course, lo scaffolder include una casella di testo per il CourseID campo perché riconosce che l'attributo DatabaseGeneratedOption.None indica che l'utente deve essere in grado di immettere il valore della chiave primaria. Tuttavia, non riconosce che perché il numero è significativo che si vuole visualizzare nelle altre visualizzazioni, quindi è necessario aggiungerlo manualmente.

In Views\Course\Edit.cshtml aggiungere un campo numero corso prima del campo Titolo . Poiché è la chiave primaria, viene visualizzata, ma non può essere modificata.

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

Esiste già un campo nascosto (Html.HiddenFor helper) per il numero del corso nella visualizzazione Modifica. L'aggiunta di un helper Html.LabelFor non elimina la necessità del campo nascosto perché non comporta l'inserimento del numero di corso nei dati pubblicati quando l'utente fa clic su Salva nella pagina Modifica.

In Views\Course\Delete.cshtml e Views\Course\Details.cshtml modificare la didascalia del reparto da "Name" a "Department" e aggiungere un campo numero corso prima del campo Titolo .

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

Eseguire la pagina Crea (visualizzare la pagina Indice corso e fare clic su Crea nuovo) e immettere i dati per un nuovo corso:

Valore Impostazione
Numero Immettere 1000.
Title Immettere Algebra.
Crediti Immettere 4.
Reparto Selezionare Matematica.

Cliccare su Crea. La pagina Indice corso viene visualizzata con il nuovo corso aggiunto all'elenco. Il nome del dipartimento nell'elenco della pagina di indice deriva dalla proprietà di navigazione, che mostra che la relazione è stata stabilita correttamente.

Eseguire la pagina Modifica (visualizzare la pagina Indice corso e fare clic su Modifica in un corso).

Modificare i dati nella pagina e fare clic su Save (Salva). La pagina Indice corso viene visualizzata con i dati aggiornati del corso.

Pagina Aggiungi ufficio agli insegnanti

Quando si modifica il record di un insegnante, è necessario essere in grado di aggiornare l'assegnazione dell'ufficio. L'entità Instructor ha una relazione uno-a-zero-o-uno con l'entità OfficeAssignment , il che significa che è necessario gestire le situazioni seguenti:

  • Se l'utente cancella l'assegnazione dell'ufficio e originariamente ha un valore, è necessario rimuovere ed eliminare l'entità OfficeAssignment .
  • Se l'utente immette un valore di assegnazione di office e originariamente era vuoto, è necessario creare una nuova OfficeAssignment entità.
  • Se l'utente modifica il valore di un'assegnazione di ufficio, è necessario modificare il valore in un'entità esistente OfficeAssignment .

Aprire InstructorController.cs ed esaminare il HttpGet Edit metodo :

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

Il codice con scaffolding non è quello desiderato. Si tratta di configurare i dati per un elenco a discesa, ma è necessaria una casella di testo. Sostituire questo metodo con il codice seguente:

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

Questo codice elimina l'istruzione ViewBag e aggiunge il caricamento eager per l'entità associata OfficeAssignment . Non è possibile eseguire il caricamento eager con il Find metodo , quindi i Where metodi e Single vengono usati per selezionare l'insegnante.

Sostituire il HttpPost Edit metodo con il codice seguente. che gestisce gli aggiornamenti delle assegnazioni di Office:

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

Il riferimento a RetryLimitExceededException richiede un'istruzione using ; per aggiungerla, posizionare il puntatore del mouse su RetryLimitExceededException. Viene visualizzato il messaggio seguente:  Messaggio di eccezione di ripetizione dei tentativi

Selezionare Mostra potenziali correzioni, quindi usare System.Data.Entity.Infrastructure

Risolvere l'eccezione di ripetizione dei tentativi

Il codice esegue le seguenti attività:

  • Modifica il nome del metodo in EditPost perché la firma è ora uguale al HttpGet metodo (l'attributo specifica che l'URL ActionName /Edit/ è ancora usato).

  • Ottiene l'entità Instructor corrente dal database tramite il caricamento eager per la proprietà di navigazione OfficeAssignment. Questo è lo stesso di quello che hai fatto nel HttpGet Edit metodo .

  • Aggiorna l'entità Instructor recuperata con valori dallo strumento di associazione di modelli. L'overload TryUpdateModel usato consente di elencare le proprietà da includere. In questo modo è possibile evitare l'overposting, come illustrato nella seconda esercitazione.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Se la posizione dell'ufficio è vuota, imposta la Instructor.OfficeAssignment proprietà su Null in modo che la riga correlata nella OfficeAssignment tabella venga eliminata.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Salva le modifiche nel database.

In Views\Instructor\Edit.cshtml, dopo gli elementi per il div campo Hire Date (Data di assunzione), aggiungere un nuovo campo per modificare la posizione dell'ufficio:

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

Eseguire la pagina (selezionare la scheda Instructors e quindi fare clic su Modifica su un insegnante). Modificare Office Location (Posizione ufficio) e fare clic su Save (Salva).

Aggiungere corsi alla pagina degli insegnanti

Gli insegnanti possono tenere un numero qualsiasi di corsi. A questo punto si migliorerà la pagina Modifica insegnante aggiungendo la possibilità di modificare le assegnazioni dei corsi usando un gruppo di caselle di controllo.

La relazione tra le Course entità e Instructor è molti-a-molti, il che significa che non si ha accesso diretto alle proprietà di chiave esterna presenti nella tabella join. Aggiungere e rimuovere entità da e verso la Instructor.Courses proprietà di navigazione.

L'interfaccia utente che consente di modificare i corsi assegnati a un insegnante è costituita da un gruppo di caselle di controllo. È visualizzata una casella di controllo per ogni corso nel database e le caselle corrispondenti ai corsi attualmente assegnati all'insegnante sono selezionate. L'utente può selezionare e deselezionare le caselle di controllo per modificare le assegnazioni dei corsi. Se il numero di corsi fosse molto maggiore, è probabile che si voglia usare un metodo diverso per presentare i dati nella visualizzazione, ma si userebbe lo stesso metodo per modificare le proprietà di navigazione per creare o eliminare relazioni.

Per fornire i dati alla visualizzazione dell'elenco di caselle di controllo, si userà una classe modello di visualizzazione. Creare AssignedCourseData.cs nella cartella ViewModels e sostituire il codice esistente con il codice seguente:

namespace ContosoUniversity.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

In InstructorController.cs sostituire il HttpGet Edit metodo con il codice seguente. Le modifiche sono evidenziate.

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

Il codice aggiunge il caricamento eager per la proprietà di navigazione Courses e chiama il nuovo metodo PopulateAssignedCourseData per fornire informazioni per la matrice di caselle di controllo tramite la classe modello di visualizzazione AssignedCourseData.

Il codice nel PopulateAssignedCourseData metodo legge tutte le Course entità per caricare un elenco di corsi usando la classe del modello di visualizzazione. Per ogni corso, il codice verifica se è presente nella proprietà di navigazione Courses dell'insegnante. Per creare una ricerca efficiente quando si verifica se un corso viene assegnato all'insegnante, i corsi assegnati all'insegnante vengono inseriti in una raccolta HashSet . La Assigned proprietà è impostata su true per i corsi a cui viene assegnato l'insegnante. La visualizzazione usa questa proprietà per determinare quali caselle di controllo devono essere visualizzate come selezionate. Infine, l'elenco viene passato alla visualizzazione in una ViewBag proprietà .

Aggiungere quindi il codice che viene eseguito quando l'utente fa clic su Save (Salva). Sostituire il EditPost metodo con il codice seguente, che chiama un nuovo metodo che aggiorna la Courses proprietà di navigazione dell'entità Instructor . Le modifiche sono evidenziate.

[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 HttpGet Edit metodo è ora diversa dal metodo , quindi il nome del metodo passa da EditPost a Edit.

Poiché la vista non dispone di una raccolta di Course entità, lo strumento di associazione di modelli non può aggiornare automaticamente la Courses proprietà di navigazione. Invece di usare lo strumento di associazione di modelli per aggiornare la Courses proprietà di navigazione, questa operazione verrà eseguita nel nuovo UpdateInstructorCourses metodo. È pertanto necessario escludere la proprietà Courses dall'associazione di modelli. Ciò non richiede alcuna modifica al codice che chiama TryUpdateModel perché si usa l'overload esplicito dell'elenco e Courses non è incluso nell'elenco di inclusione.

Se non sono state selezionate caselle di controllo, il codice in UpdateInstructorCourses inizializza la Courses proprietà di navigazione con un insieme vuoto:

if (selectedCourses == null)
{
    instructorToUpdate.Courses = new List<Course>();
    return;
}

Il codice quindi esegue il ciclo di tutti i corsi nel database e controlla ogni corso a fronte di quelli assegnati all'insegnante rispetto a quelli selezionati nella visualizzazione. Per facilitare l'esecuzione di ricerche efficienti, le ultime due raccolte sono archiviate all'interno di oggetti HashSet.

Se la casella di controllo di un corso è selezionata ma il corso non è presente nella proprietà di navigazione Instructor.Courses, il corso viene aggiunto alla raccolta nella proprietà di navigazione.

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Add(course);
    }
}

Se la casella di controllo di un corso non è selezionata ma il corso è presente nella proprietà di navigazione Instructor.Courses, il corso viene rimosso dalla proprietà di navigazione.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Remove(course);
    }
}

In Views\Instructor\Edit.cshtml aggiungere un campo Courses con una matrice di caselle di controllo aggiungendo il codice seguente immediatamente dopo gli div elementi per il OfficeAssignment campo e prima dell'elemento div per il pulsante Salva:

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

Dopo aver incollato il codice, se le interruzioni di riga e il rientro non sono simili a quanto accade qui, correggere manualmente tutto in modo che abbia un aspetto simile a quello visualizzato qui. Il rientro non deve necessariamente essere perfetto, ma le righe @</tr><tr>, @:<td>, @:</td> e @</tr> devono trovarsi in una sola riga, come illustrato. In caso contrario, viene visualizzato un errore di runtime.

Questo codice crea una tabella HTML con tre colonne. In ogni colonna è presente una casella di controllo seguita da una didascalia costituita dal numero di corso e dal titolo. Tutte le caselle di controllo hanno lo stesso nome ("selectedCourses"), che informa il gestore di associazione di modelli che devono essere considerate come un gruppo. L'attributo value di ogni casella di controllo è impostato sul valore di CourseID. Quando viene pubblicata la pagina, il gestore di associazione di modelli passa una matrice al controller costituito dai CourseID valori solo per le caselle di controllo selezionate.

Quando viene inizialmente eseguito il rendering delle caselle di controllo, quelle destinate ai corsi assegnati all'insegnante hanno checked attributi, che le seleziona (le visualizza selezionate).

Dopo aver modificato le assegnazioni dei corsi, sarà possibile verificare le modifiche quando il sito torna alla Index pagina. Pertanto, è necessario aggiungere una colonna alla tabella in tale pagina. In questo caso non è necessario usare l'oggetto ViewBag , perché le informazioni da visualizzare sono già presenti nella Courses proprietà di navigazione dell'entità Instructor passata alla pagina come modello.

In Views\Instructor\Index.cshtml aggiungere un'intestazione Courses immediatamente dopo l'intestazione di Office , come illustrato nell'esempio seguente:

<tr> 
    <th>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
    <th></th> 
</tr>

Aggiungere quindi una nuova cella di dettaglio immediatamente dopo la cella dei dettagli della posizione dell'ufficio:

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

Eseguire la pagina Instructor Index per visualizzare i corsi assegnati a ogni insegnante.

Fare clic su Modifica su un insegnante per visualizzare la pagina Modifica.

Modificare alcune assegnazioni di corso e fare clic su Salva. Le modifiche effettuate si riflettono nella pagina di indice.

Nota: l'approccio adottato qui per modificare i dati del corso dell'insegnante funziona bene quando è presente un numero limitato di corsi. Per raccolte molto più grandi, sarebbero necessari un'interfaccia utente diversa e un altro metodo di aggiornamento.

Aggiorna eliminazione confermata

In InstructorController.cs eliminare il DeleteConfirmed metodo e inserire il codice seguente al suo posto.

[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");
}

Questo codice apporta la modifica seguente:

  • Se l'insegnante viene assegnato come amministratore di qualsiasi reparto, rimuove l'assegnazione dell'insegnante da tale reparto. Senza questo codice, si otterrebbe un errore di integrità referenziale se si tenta di eliminare un insegnante assegnato come amministratore per un reparto.

Questo codice non gestisce lo scenario di un insegnante assegnato come amministratore per più reparti. Nell'ultima esercitazione si aggiungerà codice che impedisce l'esecuzione di tale scenario.

Aggiungere posizione dell'ufficio e corsi alla pagina Create (Crea)

In InstructorController.cs eliminare i HttpGet metodi e HttpPost Create e quindi aggiungere il codice seguente al loro posto:

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

Questo codice è simile a quello visualizzato per i metodi Edit, ad eccezione del fatto che inizialmente non sono selezionati corsi. Il HttpGet Create metodo chiama il PopulateAssignedCourseData metodo non perché potrebbe essere selezionato un corso, ma per fornire una raccolta vuota per il foreach ciclo nella visualizzazione . In caso contrario, il codice di visualizzazione genererà un'eccezione di riferimento Null.

Il metodo HttpPost Create aggiunge ogni corso selezionato alla proprietà di navigazione Courses prima del codice modello che controlla gli errori di convalida e aggiunge il nuovo insegnante al database. I corsi vengono aggiunti anche se sono presenti errori del modello in modo che quando si verificano errori del modello (ad esempio, l'utente ha inserito una data non valida) in modo che, quando la pagina viene riprodotta con un messaggio di errore, tutte le selezioni dei corsi effettuate vengono ripristinate automaticamente.

Si noti che, perché sia possibile aggiungere corsi alla proprietà di navigazione Courses, è necessario inizializzare la proprietà come raccolta vuota:

instructor.Courses = new List<Course>();

Anziché all'interno di codice di controllo, è possibile eseguire questa operazione nel modello Instructor modificando il getter della proprietà in modo da creare automaticamente la raccolta, se questa non esiste, come illustrato nell'esempio seguente:

private ICollection<Course> _courses;
public virtual ICollection<Course> Courses 
{ 
    get
    {
        return _courses ?? (_courses = new List<Course>());
    }
    set
    {
        _courses = value;
    } 
}

Se si modifica la proprietà Courses in questo modo, è possibile rimuovere il codice di inizializzazione esplicita della proprietà nel controller.

In Views\Instructor\Create.cshtml aggiungere una casella di testo della posizione dell'ufficio e le caselle di controllo del corso dopo il campo data di assunzione e prima del pulsante Invia .

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

Dopo aver incollato il codice, correggere le interruzioni di riga e il rientro come in precedenza per la pagina Modifica.

Eseguire la pagina Crea e aggiungere un insegnante.

Gestione di transazioni

Come illustrato nell'esercitazione sulle funzionalità CRUD di base, per impostazione predefinita Entity Framework implementa in modo implicito le transazioni. Per gli scenari in cui è necessario un maggiore controllo, ad esempio se si desidera includere operazioni eseguite all'esterno di Entity Framework in una transazione, vedere Uso delle transazioni in MSDN.

Ottenere il codice

Scaricare il progetto completato

Risorse aggiuntive

I collegamenti ad altre risorse di Entity Framework sono disponibili in ASP.NET Accesso ai dati - Risorse consigliate.

Passaggio successivo

In questa esercitazione:

  • Pagine personalizzate dei corsi
  • Aggiunta dell'ufficio alla pagina degli insegnanti
  • Aggiunta di corsi alla pagina degli insegnanti
  • Eliminazione aggiornata confermata
  • Aggiunta della posizione e dei corsi dell'ufficio alla pagina Crea

Passare all'articolo successivo per informazioni su come implementare un modello di programmazione asincrona.