1. 程式人生 > >MVC5 Entity Framework學習之實現主要的CRUD功能

MVC5 Entity Framework學習之實現主要的CRUD功能

之前 顯式 sea variable host ive spl url 轉載

在上一篇文章中,我們使用Entity Framework 和SQL Server LocalDB創建了一個MVC應用程序,並使用它來存儲和顯示數據。在這篇文章中,你將對由 MVC框架自己主動創建的CRUD(create, read, update, delete)代碼進行改動。

註意:通常我們在控制器和數據訪問層之間創建一個抽象層來實現倉儲模式。為了將註意力聚焦在怎樣使用實體框架上。這裏暫沒有使用倉儲模式。

在本篇文章中,要創建的web頁面:

技術分享

技術分享

技術分享


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

技術分享


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工具來提交表單數據

技術分享


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>

[email protected]()方法以防止跨站請求偽造攻擊。

執行項目,選擇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命令。

一個實體可能處於下面狀態之中的一個:

  • Added,數據庫並不存在該實體。SaveChanges方法必須生成一個Insert語句。

  • Unchanged。對該實體,SaveChanges方法什麽都不須要做,當從數據庫中讀取一個實體時,該實體就為這一狀態。
  • Modified,某些或全部實體的屬性值被更改,SaveChanges方法必須生成一個Update語句。
  • Deleted。

    實體已被標誌為刪除狀態,SaveChanges方法必須生成一個Delete語句。

  • Detached。實體沒有被數據庫上下文跟蹤。

在桌面應用程序中,狀態變化一般是自己主動的,當你讀取一個實體並更改它的一些屬性值。該實體的狀態會自己主動更改為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功能