Condividi tramite


Esercitazione: Aggiornare i dati correlati con EF in un'app MVC di 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 i campi chiave esterna o le proprietà di spostamento. Per le relazioni molti-a-molti, Entity Framework non espone direttamente la tabella join, quindi si aggiungono e si rimuoveno entità da e verso le proprietà di spostamento appropriate.

Le figure seguenti illustrano alcune delle pagine che verranno usate.

Course_create_page

Instructor_edit_page_with_courses

Modifica dell'insegnante con corsi

In questa esercitazione:

  • Personalizzare le pagine dei corsi
  • Aggiungere l'ufficio alla pagina degli insegnanti
  • Aggiungere corsi alla pagina degli insegnanti
  • Aggiornamento eliminato confermato
  • 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à chiave esterna ed è tutto il framework di entità necessario per caricare la proprietà di spostamento con l'entità Department 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 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 vista 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 visualizzazione passerà il nome DepartmentID all'helper DropDownList e l'helper sa quindi cercare nell'oggetto ViewBag un SelectList denominato DepartmentID.

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

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

Il HttpGetEdit 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 redisplay 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, qualsiasi reparto selezionato rimanga selezionato.

Le visualizzazioni Corso sono già scaffoldate con elenchi a discesa per il campo del reparto, ma non si vuole che l'ID reparto didascalia per questo campo, quindi apportare la modifica evidenziata seguente al file Views\Course\Create.cshtml per modificare il 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.

Normalmente 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 indica che l'utente DatabaseGeneratedOption.None deve essere in grado di immettere il valore della chiave primaria. Ma non capisce che perché il numero è significativo che si vuole visualizzarlo 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>

C'è già un campo nascosto (Html.HiddenFor helper) per il numero di corso nella visualizzazione Modifica. L'aggiunta di un helper Html.LabelFor non elimina la necessità del campo nascosto perché non causa 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 il nome del reparto didascalia 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
Number Immettere 1000.
Titolo Immettere Algebra.
Credits Immettere 4.
department Selezionare Matematica.

Fare clic 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 del corso aggiornati.

Aggiungere l'ufficio alla pagina degli 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 ha originariamente un valore, è necessario rimuovere ed eliminare l'entità OfficeAssignment .
  • Se l'utente immette un valore di assegnazione di office ed è stato originariamente vuoto, è necessario creare una nuova OfficeAssignment entità.
  • Se l'utente modifica il valore di un'assegnazione di office, è necessario modificare il valore in un'entità esistente OfficeAssignment .

Aprire InstructorController.cs e esaminare il HttpGetEdit 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 scaffolded qui non è quello desiderato. Si configurano 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 caricamento ansioso per l'entità associata OfficeAssignment . Non è possibile eseguire il caricamento ansioso con il Find metodo, quindi i Where metodi e Single vengono usati invece per selezionare l'insegnante.

Sostituire il metodo con il HttpPostEdit 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 aggiungerlo, passare il mouse su RetryLimitExceededException. Viene visualizzato il messaggio seguente:  Messaggio di eccezione di ripetizione dei tentativi

Selezionare Mostra eventuali correzioni, quindi usando System.Data.Entity.Infrastructure

Risolvere l'eccezione di ripetizione dei tentativi

Il codice esegue le seguenti attività:

  • Modifica il nome EditPost del metodo in perché la firma è ora uguale HttpGet al 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 si è fatto nel HttpGetEdit 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 viene impedito l'over-post, 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 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 Insegnanti 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 del corso usando un gruppo di caselle di controllo.

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

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 era molto maggiore, probabilmente si vuole usare un metodo diverso per presentare i dati nella visualizzazione, ma si userebbe lo stesso metodo per modificare le proprietà di spostamento 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 metodo con il HttpGetEdit 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 assegnati all'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 metodo con il EditPost 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 metodo è ora diversa dal metodo, quindi il nome del HttpGetEdit metodo cambia da EditPost indietro a Edit.

Poiché la vista non dispone di una raccolta di Course entità, l'associazione del modello non può aggiornare automaticamente la Courses proprietà di spostamento. Invece di usare il bindinger del modello per aggiornare la Courses proprietà di spostamento, lo si eseguirà nel nuovo UpdateInstructorCourses metodo. È pertanto necessario escludere la proprietà Courses dall'associazione di modelli. Questo 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 spostamento con una raccolta vuota:

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 per il div 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 sembrano simili a quelli che fanno qui, correggere manualmente tutto in modo che sia 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. Le caselle di controllo hanno lo stesso nome ("selectedCourses"), che informa il binder del modello che devono essere considerati come un gruppo. L'attributo value di ogni casella di controllo è impostato sul valore di CourseID. Quando la pagina viene pubblicata, il binding del modello passa una matrice al controller costituito dai valori solo per le caselle di CourseID controllo selezionate.

Quando le caselle di controllo vengono inizialmente sottoposte a rendering, quelle che sono per i corsi assegnati all'insegnante hanno checked attributi, che li seleziona (visualizzali controllati).

Dopo aver modificato le assegnazioni del corso, si vuole essere in grado di verificare le modifiche quando il sito torna alla Index pagina. È quindi necessario aggiungere una colonna alla tabella in tale pagina. In questo caso non è necessario usare l'oggetto ViewBag , perché le informazioni che si desidera visualizzare sono già nella Courses proprietà di spostamento dell'entità Instructor passata alla pagina come modello.

In Views\Instructor\Index.cshtml aggiungere un'intestazione Corsi 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 di dettaglio 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 Indice istruttore 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 esiste un numero limitato di corsi. Per raccolte molto più grandi, sarebbero necessari un'interfaccia utente diversa e un altro metodo di aggiornamento.

Aggiornamento eliminato confermato

In InstructorController.cs eliminare il metodo e inserire il DeleteConfirmed 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à il 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 HttpPostCreate 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 Di modifica, ad eccezione del fatto che inizialmente non sono selezionati corsi. Il HttpGetCreate metodo chiama il PopulateAssignedCourseData metodo non perché potrebbero essere selezionati corsi, ma per fornire una raccolta vuota per il foreach ciclo nella vista ( in caso contrario, il codice di visualizzazione genererà un'eccezione di riferimento Null).

Il metodo HttpPost Create aggiunge ogni corso selezionato alla proprietà di spostamento Courses prima del codice modello che verifica 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 ci sono errori del modello (ad esempio, l'utente ha chiave una data non valida) in modo che quando la pagina viene ririeseguita 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 percorso ufficio e 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 rientro come si è fatto in precedenza per la pagina Modifica.

Eseguire la pagina Crea e aggiungere un insegnante.

Gestione di transazioni

Come illustrato nell'esercitazione sulla 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 dei corsi personalizzati
  • 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.