標籤:orm img 並儲存 value center _id bad att virt
在上一篇文章中,我們使用Entity Framework 和SQL Server LocalDB建立了一個MVC應用程式,並使用它來儲存和顯示資料。在這篇文章中,你將對由 MVC架構自己主動建立的CRUD(create, read, update, delete)代碼進行改動。
注意:通常我們在控制器和資料訪問層之間建立一個抽象層來實現倉儲模式。為了將注意力聚焦在怎樣使用Entity Framework上。這裡暫沒有使用倉儲模式。
在本篇文章中,要建立的web頁面:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvam9obnNvbmJsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >
1.建立一個Details頁面
由架構代碼產生的Students Index頁面暫沒有考慮Enrollments屬性,由於該屬性是一個集合。在Details頁面中,我們將在HTML表格中顯示集合中的內容。
開啟 Controllers\StudentController.cs。能夠看到相應Details視圖的Details方法使用Find方法來檢索單個學生實體:
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);}
Details方法的id參數來自Index頁面中Details連結,稱為路由資料(route data)。
路由資料是指在路由表中指定,通過URL傳遞,由模型繫結器接收的資料。
例如以下所看到的,預設路由指定了controller, action和 id
routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
在以下的 URL中。預設路由將Instructor 映射為controller, Index映射為action, 1 映射為 id
http://localhost:1230/Instructor/Index/1?courseID=2021
"?
courseID=2021" 是查詢字串, 假設你將id作為查詢字串,模型繫結器也能正常解析
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
在Razor視圖中,由ActionLink語句來建立URL,如以下的代碼中id參數匹配預設路由。所以id被作為進路由資料
@Html.ActionLink("Select", "Index", new { id = item.PersonID })
以下的代碼中courseID參數 不匹配預設路由,所以courseID被作為查詢字串
@Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
開啟Views\Student\Details.cshtml,每一個欄位都使用DisplayFor協助器來顯示資料,如以下的代碼所看到的:
<dt> @Html.DisplayNameFor(model => model.LastName)</dt><dd> @Html.DisplayFor(model => model.LastName)</dd>
在EnrollmentData欄位之後。</dl>標籤之前,加入以下的代碼
<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>
假設代碼縮排不對,能夠使用Ctrl-K-D快速鍵來糾正它。
上面的代碼遍曆Enrollments導覽屬性中的實體,對於每個Enrollment實體。顯示出Course Title 和Grade。Course Title是從Enrollments實體中的Course導覽屬性中的Course實體中擷取的,全部這些資料豆是在須要時自己主動從資料庫檢索的。(換句話說,這裡使用的是延遲載入。你沒有為Courses導覽屬性指定預先載入。所以在同一次查詢中,僅僅檢索了Students資料而沒有檢索enrollments資料。相反。在第一次試圖訪問Enrollments導覽屬性時,會建立一個新的查詢並發送到資料庫。
執行項目,選擇Students 選項卡並點擊名為Alexander Carson的Details 連結。(假設你按Ctrl+F5,直接開啟Details.cshtml,會得到HTTP 400錯誤,由於Visual Studio會直接開啟Details頁面卻沒有指定不論什麼一個studen,路由匹配錯誤導致程式出錯。在這樣的情況下。你僅僅須要從URL中刪除Student/Details然後重試)
能夠看到所選學生的courses 和grades
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvam9obnNvbmJsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >
2.更新Create 頁面
開啟Controllers\StudentController.cs,使用以下的代碼替換HttpPost Create方法
[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) { //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); }
上面的代碼將ASP.NET MVC模型繫結器建立的Student實體加入到Students 實體集並儲存到資料庫中。
(模型繫結器能夠讓你更easy的提交表單資料,能夠將提交的表單值轉換為CLR值並將它們作為參數傳遞給Controller中的方法。在本項目中,模型繫結器使用了表單集合中的屬性值執行個體化了一個Student 實體)
這裡刪除了Bind 屬性中的ID參數。由於ID是primary key。SQL Server在插入資料時會自己主動設定該值。
安全注意:ValidateAntiForgeryToken屬性有助於防止跨站請求偽造(cross-site request forgery)攻擊。可是須要在視圖中設定對應的Html.AntiForgeryToken()語句。
Bind屬效能夠防止過份提交(over-posting)。舉例來說,如果Student實體中包括一個Secret 欄位,你不希望在Web頁面中更新它
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; } }
即使在Web頁面中沒有Secret欄位,駭客也能夠通過工具比如Fiddler或者JavaScript 將表單資料包含Secret值提交到server。假設不使用Bind屬性來限制模型綁器須要的欄位。模型繫結器會將接收到的Secret值更新至資料庫中。以下的是通過Fiddler工具來提交表單資料
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvam9obnNvbmJsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >
OverPost值將會被成功的更新至資料庫,這是你不希望看到的。
為了安全起見,最好使用Bind屬性的Include參數,也能夠使用Exclude參數排除那些你不想要更新的屬性。
可是這裡推薦使用Include,由於假設你在實體中加入了一個新的屬性,Exclude並不會將這個新加入的屬性排除在外。
還有一種替代方法是在模型繫結時使用視圖模型,視圖模型中僅僅包括你想要綁定的屬性。
除了Bind屬性。上面的代碼中僅僅須要增加try-catch塊,假設在儲存更改時引發DataException異常,就會在頁面中顯示對應的錯誤資訊。
DataException異常有時是由外來事件引發而不是由於程式錯誤,所以建議使用者重試。記住在生產環境下,全部的應用程式錯誤都應該被記錄下來。
Views\Student\Create.cshtml中的代碼和Details.cshtml中的非常相似,除了DisplayFor被EditorFor和ValidationMessageFor協助器替代
<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也包括了@Html.AntiForgeryToken()方法以防止跨站請求偽造攻擊。
執行項目,選擇Students選項卡。並點擊Create New
輸入姓名和無效的日期,然後單擊Create查看錯誤訊息
預設情況下使用的是server端驗證,以後會教大家通過加入屬性來產生client驗證,以下的代碼展示了Create 方法中的模型驗證檢查
if (ModelState.IsValid){ db.Students.Add(student); db.SaveChanges(); return RedirectToAction("Index");}
改動日期為一個有效值,點擊Create,能夠看到新加入的Student資訊
3.更新Edit HttpPost頁面
在Controllers\StudentController.cs中,HttpGet Edit方法(沒有使用HttpPost屬性的那一個)和Details方法一樣使用Find方法來檢索所選擇的Student實體。
使用以下的代碼替換HttpPost Edit方法:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include = "ID,LastName,FirstMidName,EnrollmentDate")] Student student) { try { if (ModelState.IsValid) { db.Entry(student).State = EntityState.Modified; 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); }
上面的代碼類似於HttpPost Create方法。但不同的是這裡在實體中設定了一個標誌位來指明它已經被更改,而不是將由模型繫結器建立的實體加入到實體集。
當調用SaveChanges方法時,Modified標誌會導致 Entity Framework建立SQL語句並更新資料庫。資料庫中該行的全部列都將被更新,包含那些使用者並沒有更改的,並忽略並發衝突。
實體狀態、附加和SaveChanges方法
資料庫上下文會一直跟蹤記憶體中的實體是否與資料庫中的行保持同步。並由此決定當調用SaveChanges方法時會發生什麼,比如,當你調用Add方法加入實體時。該實體的狀態會被設定為Added,然後當調用SaveChanges方法時。資料庫上下文會產生一個SQL Insert命令。
一個實體可能處於下面狀態之中的一個:
在傳統型應用程式中,狀態變化一般是自己主動的,當你讀取一個實體並更改它的一些屬性值。該實體的狀態會自己主動更改為Modified,然後當你調用SaveChanges方法時。Entity Framework 會產生一個SQL Update來更新資料庫。
DbContext 在讀取一個實體並將其呈現到頁面上後就會被銷毀。
當HttpPost Edit方法被調用。此時會產生一個新的請求和DbContext 執行個體,所以你必須手動設定實體狀態為Modified。然後當你調用SaveChanges方法時,Entity Framework 會更新資料庫行的全部列,由於資料庫上下文沒有辦法知道你究竟更改了哪些屬性。
假設你希望SQL Update語句僅僅更新那些使用者實際更改的欄位,你能夠先將原來的值以某種方法(比方隱藏欄位)儲存起來,這樣在調用HttpPost Edit方法時就能夠使用它們,然後你能夠使用原來的值來建立一個Student實體,調用Attach方法。並使用新的值更新該實體,最後調用SaveChanges方法。
Views\Student\Edit.cshtml 中的HTML 和Razor代碼與Create.cshtml中的非常類似。
執行項目。選擇Students選項卡,點擊當中一個學生的Edit連結
改動當中的值,點擊Save。能夠在Index頁面中看到已經改動過的資料
4.更新Delete頁面
在Controllers\StudentController.cs中,由模板產生的HttpGet Delete方法使用Find方法檢索所選的Student實體。
然而,當調用SaveChanges方法失敗時為了顯示自己定義的錯誤資訊。你須要向該方法和相相應的視圖中加入一些功能。
就像update和create操作,delete操作也須要兩個動作方法。用於響應Get請求的方法用來顯示一個能夠讓使用者<批准或取消delete操作的視圖。假設用確認運行delete操作,此時會產生一條POST請求。並調用HttpPost Delete方法,該方法運行真正的delete操作。
在HttpPost Delete方法中加入try-catch塊能夠用來捕獲資料庫更新時可能出現的不論什麼錯誤,假設出現了錯誤。則HttpPost Delete方法會調用HttpGet Delete方法。並向其傳遞一個參數指明發生了錯誤,然後HttpGet Delete會顯示一個錯誤資訊,並給使用者一個取消或重試的機會。
使用以下的代碼替換HttpGet Delete方法:
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);}
上面的代碼接受一個可選擇參數,指明該方法在儲存更改出現錯誤後是否被調用。
當HttpGet Delete方法不是因為出現錯誤而被調用的話。該參數值為false。當HttpPost Delete出現了錯誤而調用HttpGet Delete方法時該參數為true並在對應的視圖上顯示錯誤資訊。
使用以下的代碼替換HttpPost Delete方法(名稱為DeleteConfirmed的那個)。此方法用來運行真正的delete操作並捕獲不論什麼資料庫更新錯誤
[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");}
上面的代碼從資料庫中檢索要刪除的實體。然後調用Remove方法將實體的狀態設定為Deleted,最後調用SaveChanges方法並產生一條SQL Delete命令。另外你也能夠將方法名DeleteConfirmed改為Delete。架構代碼將HttpPost Delete方法命名為DeleteConfirmed是為了為其設定一個獨一無二的名稱(CLR重載方法須要有不同的參數)。如今遵守MVC的約定。HttpPost和HttpGet delete方法使用了同樣的名字,並為它們設定不同的參數。
假設你想提高高訪問量應用程式的效能,你要避免使用不必要的SQL查詢。
使用以下的代碼替換Find和Remove方法
Student studentToDelete = new Student() { ID = id };db.Entry(studentToDelete).State = EntityState.Deleted;
上面的代碼使用唯一的主鍵值執行個體化了一個學生實體並設定實體狀態為Deleted。這便是Entity Framework為了刪除一個實體所須要做的動作。
如前所述HttpGet Delete方法並不會運行資料刪除操作,在一個Get請求響應中運行delete操作(運行不論什麼edit操作、create操作或者其他對資料變更的操作)將帶來安全風險。
在Views\Student\Delete.cshtml中加入錯誤資訊
<h2>Delete</h2><p class="error">@ViewBag.ErrorMessage</p><h3>Are you sure you want to delete this?</h3>
執行項目,點擊Students選項卡,點擊當中一個學生的Delete連結:
點擊Delete。你會看到在Index頁面中該學生已經被刪除。
5.確保資料庫連接適時關閉
要確保資料庫連接被正確的關閉並釋放所佔用的資源,在你使用完資料庫上下文時,必需要將其銷毀。這就是為什麼架構代碼在StudentController.cs的最後部分提供了一個Dispose方法
protected override void Dispose(bool disposing){ db.Dispose(); base.Dispose(disposing);}
Controller類實現了IDisposeable介面,所以上面的代碼通過重寫Dispose(bool)方法來顯式的銷毀資料庫上下文執行個體。
6.處理事務
預設情況下,Entity Framework隱式的實現交易處理。當你對多行或者多個表變更後調用SaveChanges方法,Entity Framework會自己主動確保所有更改要麼所有成功要麼所有失敗。假設已經做完一些更改後發生了一個錯誤。那麼所有的更改包含已做完的都將自己主動復原。
歡迎轉載。請註明文章出處:http://blog.csdn.net/johnsonblog/article/details/38711659
部落格搬家啦。我的小站:MVC5 Entity Framework學習(2):實現主要的CRUD功能
還大家一個健康的網路環境。從你我做起
項目原始碼:https://github.com/johnsonz/MvcContosoUniversity
THE END
MVC5 Entity Framework學習之實現主要的CRUD功能