Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den föregående självstudien skapade du ett MVC-program som lagrar och visar data med hjälp av Entity Framework (EF) 6 och SQL Server LocalDB. I den här självstudien granskar och anpassar du den CRUD-kod (create, read, update, delete) som MVC-ställningskod automatiskt skapar åt dig i styrenheter och vyer.
Note
Det är vanligt att implementera lagringsplatsens mönster för att skapa ett abstraktionslager mellan kontrollanten och dataåtkomstskiktet. För att hålla de här självstudierna enkla och fokuserade på att lära ut hur man använder SJÄLVA EF 6 använder de inte repositories. Information om hur du implementerar repositorier finns i ASP.NET Data Access Content Map.
Här är exempel på de webbsidor som du skapar:
I den här handledningen kommer du att:
- Skapa en detaljsida
- Uppdatera sidan Skapa
- Uppdatera metoden HttpPost-redigering
- Uppdatera sidan Ta bort
- Stäng databasanslutningar
- Hantera transaktioner
Prerequisites
Skapa en informationssida
Den kod som skapats för sidan Studenter Index utelämnade Enrollments egenskapen eftersom egenskapen innehåller en samling. På sidan Details visar du innehållet i samlingen i en HTML-tabell.
I Controllers\StudentController.cs använder åtgärdsmetoden för Details-vyn metoden Find för att hämta en enda Student-entitet.
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
Nyckelvärdet skickas till metoden som parametern id och kommer från routningsdata i hyperlänken Detaljer på sidan Index.
Tips Routningsdata
Routningsdata är data som modellbindaren hittade i ett URL-segment som anges i routningstabellen. Exempelvis anger standardvägen: controller, action och id segment:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I följande URL mappar Instructor standardvägen som controller, Index som action och 1 som id; dessa är routningsdatavärden.
http://localhost:1230/Instructor/Index/1?courseID=2021
?courseID=2021 är ett värde i frågesträngen. Modellbindningen fungerar också om du skickar id som ett frågesträngsvärde:
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
URL:erna skapas av ActionLink -instruktioner i Razor-vyn. I följande kod matchar parametern id standardvägen, så id läggs till i routningsdata.
@Html.ActionLink("Select", "Index", new { id = item.PersonID })
I följande kod courseID matchar inte en parameter i standardvägen, så den läggs till som en frågesträng.
@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
Så här skapar du sidan Information
Öppna Views\Student\Details.cshtml.
Varje fält visas med hjälp av en
DisplayForhjälp, som du ser i följande exempel:<dt> @Html.DisplayNameFor(model => model.LastName) </dt> <dd> @Html.DisplayFor(model => model.LastName) </dd>Efter fältet
EnrollmentDateoch omedelbart före den avslutande</dl>taggen lägger du till den markerade koden för att visa en lista över registreringar, som du ser i följande exempel:<dt> @Html.DisplayNameFor(model => model.EnrollmentDate) </dt> <dd> @Html.DisplayFor(model => model.EnrollmentDate) </dd> <dt> @Html.DisplayNameFor(model => model.Enrollments) </dt> <dd> <table class="table"> <tr> <th>Course Title</th> <th>Grade</th> </tr> @foreach (var item in Model.Enrollments) { <tr> <td> @Html.DisplayFor(modelItem => item.Course.Title) </td> <td> @Html.DisplayFor(modelItem => item.Grade) </td> </tr> } </table> </dd> </dl> </div> <p> @Html.ActionLink("Edit", "Edit", new { id = Model.ID }) | @Html.ActionLink("Back to List", "Index") </p>Om kod indraget är fel när du har klistrat in koden trycker du på Ctrl+K, Ctrl+D för att formatera den.
Den här koden loopar igenom entiteterna i navigeringsegenskapen
Enrollments. För varjeEnrollmententitet i egenskapen visas kursrubriken och betyget. Kurstiteln hämtas från entitetenCoursesom lagras iCourseentitetens navigeringsegenskapEnrollments. Alla dessa data hämtas automatiskt från databasen när de behövs. Med andra ord använder du lazy loading här. Du angav inte eager loading för navigeringsegenskapenCourses, så registreringarna hämtades inte i samma fråga som hämtade eleverna. Första gången du försöker komma åt navigeringsegenskapenEnrollmentsskickas i stället en ny fråga till databasen för att hämta data. Du kan läsa mer om lazy loading och eager loading i självstudien Reading Related Data senare i den här serien.Öppna sidan Detaljer genom att starta programmet (Ctrl+F5), välja fliken Studenter och sedan klicka på länken Detaljer för Alexander Carson. (Om du trycker på Ctrl+F5 medan filen Details.cshtml är öppen får du ett HTTP 400-fel. Det beror på att Visual Studio försöker köra sidan Details, men den nåddes inte från en länk som anger vilken elev som ska visas. Om det händer tar du bort "Student/Details" från URL:en och försöker igen, eller stänger webbläsaren, högerklickar på projektet och klickar på Visa>Visa i webbläsare.)
Du ser listan över kurser och betyg för den valda eleven.
Stäng webbläsaren.
Uppdatera sidan Skapa
I filen Controllers\StudentController.cs ersätter du åtgärdsmetoden HttpPostAttribute
Createmed följande kod. Den här koden lägger till etttry-catchblock och tar bortIDfrån BindAttribute attributet för den scaffolded -metoden:[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "LastName, FirstMidName, EnrollmentDate")]Student student) { try { if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); } } catch (DataException /* 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(student); }Den här koden lägger till entiteten
Studentsom skapats av ASP.NET MVC-modellbindaren till entitetsuppsättningenStudentsoch sparar sedan ändringarna i databasen. Modellbindare refererar till ASP.NET MVC-funktionalitet som gör det enklare för dig att arbeta med data som skickas av ett formulär. En modellbindare konverterar inskickade formulärvärden till CLR-typer och skickar dem som parametrar till åtgärdsmetoden. I det här fallet instansierar modellbindaren en entitetStudentåt dig med hjälp av egenskapsvärden frånFormsamlingen.Du har tagit bort
IDfrån attributet Bind eftersomIDär det primära nyckelvärde som SQL Server anger automatiskt när raden infogas. Indata från användaren ställer inte in värdetID.Säkerhetsvarning – Attributet
ValidateAntiForgeryTokenhjälper till att förhindra begärandeförfalskning mellan webbplatser-attacker. Det kräver en motsvarandeHtml.AntiForgeryToken()sats i vyn, som du kommer att se senare.Bind-attributet är ett sätt att skydda mot överpostning vid skapande. Anta till exempel att entitetenStudentinnehåller enSecretegenskap som du inte vill att den här webbsidan ska ange.public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public string Secret { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }Även om du inte har något
Secret-fält på webbsidan kan en hackare använda ett verktyg som Fiddler eller skriva JavaScript för att skicka in ettSecret-formulärvärde. Utan attributet BindAttribute som begränsar de fält som modellbindaren använder när den skapar enStudent-instans, skulle modellbindaren fånga upp formulärvärdetSecretoch använda det för att skapa entitetsinstansenStudent. Då skulle det värde som hackaren angav förSecret-formulärfältet uppdateras i din databas. Följande bild visar verktyget Fiddler som lägger till fältetSecret(med värdet "OverPost") i de postade formulärvärdena.
Värdet "OverPost" skulle då framgångsrikt läggas till i egenskapen
Secretför den infogade raden, även om du aldrig avsåg att webbsidan skulle kunna ställa in den egenskapen.Det är bäst att använda parametern
Includemed attributetBindför att explicit ange fält. Du kan också använda parameternExcludeför att blockera fält som du vill exkludera. Anledningen till attIncludeär säkrare är att när du lägger till en ny egenskap i entiteten skyddas inte det nya fältet automatiskt av enExcludelista.Du kan förhindra överpublicering i redigeringsscenarier genom att först läsa entiteten från databasen och sedan anropa
TryUpdateModel, och skicka in en explicit lista över tillåtna egenskaper. Det är den metod som används i de här självstudierna.Ett alternativt sätt att förhindra overposting som många utvecklare föredrar är att använda vymodeller i stället för entitetsklasser med modellbindning. Inkludera endast de egenskaper som du vill uppdatera i vymodellen. När MVC-modellbindningen har slutförts kopierar du egenskaperna för visningsmodellen till entitetsinstansen, eventuellt med hjälp av ett verktyg som AutoMapper. Använd db. Post på entitetsinstansen för att ange dess tillstånd till Oförändrat och ange sedan Property("PropertyName"). IsModified till true för varje entitetsegenskap som ingår i vymodellen. Den här metoden fungerar både i redigerings- och skapandescenarier.
Förutom
Bindattributet ärtry-catchblocket den enda ändring som du har gjort i den scaffolded koden. Om ett undantag som härleds från DataException fångas när ändringarna sparas visas ett allmänt felmeddelande. DataException undantag orsakas ibland av något utanför programmet i stället för ett programmeringsfel, så användaren uppmanas att försöka igen. Även om det inte implementeras i det här exemplet skulle en produktionsfärdig applikation logga undantaget. Mer information finns i avsnittet Log for insight i Övervakning och telemetri (Skapa Real-World Cloud Apps med Azure).Koden i Views\Student\Create.cshtml liknar det du såg i Details.cshtml, förutom att
EditorForochValidationMessageForhelpermetoder används för varje fält i stället förDisplayFor. Här är den relevanta koden:<div class="form-group"> @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName) </div> </div>Create.cshtml innehåller även
@Html.AntiForgeryToken(), som fungerar med attributetValidateAntiForgeryTokeni controllern för att förhindra förfalskningsattacker mellan webbplatser.Inga ändringar krävs i Create.cshtml.
Öppna sidan genom att starta programmet, välja fliken Studenter och klicka sedan på Skapa ny.
Ange namn och ett ogiltigt datum och klicka på Skapa för att se felmeddelandet.
Det här är validering på serversidan som du får som standard. I en senare självstudie får du se hur du lägger till attribut som genererar kod för validering på klientsidan. Följande markerade kod visar modellverifieringskontrollen i metoden Create.
if (ModelState.IsValid) { db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index"); }Ändra datumet till ett giltigt värde och klicka på Skapa för att se den nya eleven visas på sidan Index .
Stäng webbläsaren.
Uppdatera Edit-metoden för HttpPost
HttpPostAttribute
EditErsätt action-metoden med följande kod:[HttpPost, ActionName("Edit")] [ValidateAntiForgeryToken] public ActionResult EditPost(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } var studentToUpdate = db.Students.Find(id); if (TryUpdateModel(studentToUpdate, "", new string[] { "LastName", "FirstMidName", "EnrollmentDate" })) { try { db.SaveChanges(); return RedirectToAction("Index"); } catch (DataException /* 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(studentToUpdate); }Note
I Controllers\StudentController.cs använder
HttpGet Editmetoden (den utanFindattributet)HttpPostmetoden för att hämta den valdaStudententiteten, som du såg iDetailsmetoden. Du behöver inte ändra den här metoden.Dessa ändringar implementerar bästa praxis för säkerhet för att förhindra overposting. Scaffolder genererade ett
Bindattribut och lade till entiteten som skapades av modellbindaren i entitetsuppsättningen med flaggan Ändrad. Koden rekommenderas inte längre eftersom attributetBindrensar bort redan befintliga data i fält som inte anges i parameternInclude. I framtiden uppdateras MVC-styrenhetens scaffolder så att den inte genererarBindattribut för Redigeringsmetoder.Den nya koden läser den befintliga entiteten och anropar TryUpdateModel för att uppdatera fält från användarindata i inskickade formulärdata. Entity Frameworks automatiska ändringsspårning sätter flaggan EntityState.Modified på entiteten. När metoden SaveChanges anropas gör flaggan Modified att Entity Framework skapar SQL-instruktioner för att uppdatera databasraden. Samtidighetskonflikter ignoreras, och alla kolumner i databasraden uppdateras, inklusive de som användaren inte ändrade. (En senare självstudie visar hur du hanterar samtidighetskonflikter, och om du bara vill att enskilda fält ska uppdateras i databasen kan du ange entiteten till EntityState.Unchanged och ange enskilda fält till EntityState.Modified.)
För att förhindra overposting listas de fält som du vill ska vara uppdateringsbara via redigeringssidan i parametrarna
TryUpdateModel. För närvarande finns det inga extra fält som du skyddar, men att ange de fält som du vill att modellbindaren ska binda till säkerställer att om du lägger till fält i datamodellen i framtiden skyddas de automatiskt tills du uttryckligen lägger till dem här.Till följd av dessa ändringar är metodsignaturen för HttpPost-metoden Edit densamma som för HttpGet-metoden Edit. Därför har du döpt om metoden till EditPost.
Tip
Entitetstillstånd och metoderna Attach och SaveChanges
Databaskontexten håller reda på om entiteter i minnet är synkroniserade med motsvarande rader i databasen, och den här informationen avgör vad som händer när du anropar
SaveChangesmetoden. När du till exempel skickar en ny entitet till metoden Lägg till anges den entitetens tillstånd tillAdded. När du sedan anropar metoden SaveChanges utfärdar databaskontexten ett SQL-kommandoINSERT.En entitet kan vara i något av följande tillstånd:
-
Added. Entiteten finns ännu inte i databasen. MetodenSaveChangesmåste utfärda enINSERT-instruktion. -
Unchanged. Inget behöver göras med den här entiteten med metodenSaveChanges. När du läser en entitet från databasen börjar entiteten med den här statusen. -
Modified. Vissa eller alla entitets egenskapsvärden har ändrats. MetodenSaveChangesmåste utfärda enUPDATE-instruktion. -
Deleted. Entiteten har markerats för borttagning. MetodenSaveChangesmåste utfärda enDELETE-instruktion. -
Detached. Entiteten spåras inte av databaskontexten.
I ett skrivbordsprogram anges vanligtvis tillståndsändringar automatiskt. I en skrivbordstyp av program läser du en entitet och gör ändringar i vissa av dess egenskapsvärden. Detta gör att dess entitetstillstånd ändras automatiskt till
Modified. När du sedan anroparSaveChangesgenererar Entity Framework en SQL-instruktionUPDATEsom endast uppdaterar de faktiska egenskaper som du har ändrat.Webbapparnas frånkopplade karaktär tillåter inte den här kontinuerliga sekvensen. DbContext som läser en entitet tas bort när en sida återges. När åtgärdsmetoden anropas görs en ny begäran och du har en ny instans av
HttpPost, så du måste manuellt ange entitetstillståndet tillEditSedan när du anropar uppdaterar Entity Framework alla kolumner i databasraden, eftersom kontexten inte har något sätt att veta vilka egenskaper du har ändrat.Modified.SaveChangesOm du vill att SQL-instruktionen
Updateendast ska uppdatera de fält som användaren faktiskt har ändrat kan du spara de ursprungliga värdena på något sätt (till exempel dolda fält) så att de är tillgängliga närHttpPostEditmetoden anropas. Sedan kan du skapa enStudent-entitet med hjälp av de ursprungliga värdena, anropa metodenAttachmed den ursprungliga versionen av entiteten, uppdatera entitetens värden till de nya värdena och sedan anropaSaveChanges.. För mer information, se Entitetstillstånd och SaveChanges och Lokal data.HTML- och Razor-koden i Views\Student\Edit.cshtml liknar det du såg i Create.cshtml och inga ändringar krävs.
-
Kör sidan genom att starta programmet, välja fliken Studenter och sedan klicka på en Redigera hyperlänk.
Ändra vissa data och klicka på Spara. Du ser ändrade data på sidan Index.
Stäng webbläsaren.
Uppdatera sidan Ta bort
I Controllers\StudentController.cs använder HttpGetAttribute mallkoden för DeleteFind metoden för att hämta den valda Student entiteten, som du såg i Details och Edit metoderna. Men för att implementera ett anpassat felmeddelande när anropet till SaveChanges misslyckas lägger du till viss funktionalitet i den här metoden och dess motsvarande vy.
Som du såg för att uppdatera och skapa åtgärder kräver borttagningsåtgärder två åtgärdsmetoder. Metoden som anropas som svar på en GET-begäran visar en vy som ger användaren en chans att godkänna eller avbryta borttagningsåtgärden. Om användaren godkänner det skapas en POST-begäran. När det händer HttpPostDelete anropas metoden och den metoden utför faktiskt borttagningsåtgärden.
Du lägger till ett try-catch block i HttpPostAttributeDelete metoden för att hantera eventuella fel som kan uppstå när databasen uppdateras. Om ett fel inträffar HttpPostAttributeDelete anropar HttpGetAttributeDelete metoden metoden och skickar den en parameter som anger att ett fel har inträffat. Metoden HttpGetAttributeDelete visar sedan bekräftelsesidan igen tillsammans med felmeddelandet, vilket ger användaren möjlighet att avbryta eller försöka igen.
HttpGetAttribute
DeleteErsätt åtgärdsmetoden med följande kod, som hanterar felrapportering:public ActionResult Delete(int? id, bool? saveChangesError=false) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } if (saveChangesError.GetValueOrDefault()) { ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator."; } Student student = db.Students.Find(id); if (student == null) { return HttpNotFound(); } return View(student); }Den här koden tar emot en valfri parameter som anger om metoden anropades efter ett misslyckat försök att spara ändringar. Den här parametern är
falsenärHttpGetDeletemetoden anropas utan ett tidigare fel. När den anropas avHttpPostDeletemetoden som svar på ett databasuppdateringsfel ärtrueparametern och ett felmeddelande skickas till vyn.HttpPostAttribute
DeleteErsätt åtgärdsmetoden (med namnetDeleteConfirmed) med följande kod, som utför den faktiska borttagningsåtgärden och fångar upp eventuella databasuppdateringsfel.[HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(int id) { try { Student student = db.Students.Find(id); db.Students.Remove(student); db.SaveChanges(); } catch (DataException/* dex */) { //Log the error (uncomment dex variable name and add a line here to write a log. return RedirectToAction("Delete", new { id = id, saveChangesError = true }); } return RedirectToAction("Index"); }Den här koden hämtar den valda entiteten och anropar sedan metoden Ta bort för att ange entitetens status till
Deleted. NärSaveChangesanropas genereras ett SQL-kommandoDELETE. Du har också ändrat åtgärdsmetodnamnet frånDeleteConfirmedtillDelete. Genererad kod gavHttpPost-metoden namnetDeleteDeleteConfirmedför att ge metodenHttpPosten unik signatur. (CLR kräver att överlagrade metoder har olika metodparametrar.) Nu när signaturerna är unika kan du hålla dig till MVC-konventionen och använda samma namn förHttpPostmetoderna ochHttpGetta bort.Om det är en prioritet att förbättra prestandan i ett program med hög volym, kan du undvika en onödig SQL-fråga för att hämta raden genom att ersätta de kodrader som anropar metoderna
FindochRemovemed följande kod:Student studentToDelete = new Student() { ID = id }; db.Entry(studentToDelete).State = EntityState.Deleted;Den här koden instansierar en entitet
Studentmed endast primärnyckelvärdet och anger sedan entitetstillståndet tillDeleted. Det är allt som Entity Framework behöver för att ta bort entiteten.Som nämnts
HttpGetDeletetar metoden inte bort data. Att utföra en borttagningsåtgärd som svar på en GET-begäran (eller för den delen att utföra en redigeringsåtgärd, skapa åtgärd eller någon annan åtgärd som ändrar data) skapar en säkerhetsrisk.I Views\Student\Delete.cshtml lägger du till ett felmeddelande mellan
h2rubriken ochh3rubriken, enligt följande exempel:<h2>Delete</h2> <p class="error">@ViewBag.ErrorMessage</p> <h3>Are you sure you want to delete this?</h3>Kör sidan genom att starta programmet, välja fliken Studenter och sedan klicka på en Ta bort hyperlänk.
Välj Ta bort på sidan med texten Vill du ta bort det här?.
Sidan Index visas utan den borttagna eleven. (Du kommer att se ett exempel på felhanteringskoden i praktiken i självstudiekursen om samtidighet.)
Stäng databasanslutningar
För att stänga databasanslutningar och frigöra resurserna de använder så snart som möjligt avslutar du kontextinstansen när du är klar med den. Det är därför som den genererade koden innehåller en Dispose-metod i slutet av StudentController klassen i StudentController.cs, som du ser i följande exempel:
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
Controller Basklassen implementerar redan gränssnittet, så den här IDisposable koden lägger helt enkelt till en överskrivning av Dispose(bool) metoden för att uttryckligen frigöra kontextinstansen.
Hantera transaktioner
Som standard implementerar Entity Framework implicit transaktioner. I scenarier där du gör ändringar i flera rader eller tabeller och sedan anropar SaveChangesser Entity Framework automatiskt till att alla dina ändringar lyckas eller att alla misslyckas. Om vissa ändringar görs först och sedan inträffar ett fel, återställs dessa ändringar automatiskt. För scenarier där du behöver mer kontroll, till exempel om du vill inkludera åtgärder som utförs utanför Entity Framework i en transaktion, se Arbeta med transaktioner.
Ytterligare resurser
Nu har du en fullständig uppsättning sidor som utför enkla CRUD-åtgärder för Student entiteter. Du använde MVC-hjälpen för att generera användargränssnittselement för datafält. Mer information om MVC-hjälpen finns i Rendering a Form Using HTML Helpers (artikeln är för MVC 3 men är fortfarande relevant för MVC 5).
Länkar till andra EF 6-resurser finns i ASP.NET Data Access – Rekommenderade resurser.
Nästa steg
I den här handledningen kommer du att:
- En detaljsida har skapats
- Sidan Skapa har uppdaterats
- HttpPost Edit-metoden har uppdaterats
- Sidan Ta bort har uppdaterats
- Stängda databasanslutningar
- Hanterade transaktioner
Gå vidare till nästa artikel för att lära dig hur du lägger till sortering, filtrering och sidindelning i projektet.