CRUD (創(chuàng)建祷蝌,讀取,更新和刪除)
在上一章節(jié)中痴昧,您創(chuàng)建了一個使用 Entity Framework 和 SQL Server LocalDB 存儲和顯示數(shù)據(jù)的 MVC 應(yīng)用程序稽穆。 在本章中,將復(fù)習(xí)并學(xué)習(xí)如何自定義 MVC 基架(腳手架)在控制器和視圖中生成的 CRUD 代碼赶撰。
Repository (倉儲)模式是一種常見的用于在控制器和數(shù)據(jù)訪問層之間實現(xiàn)抽象的方法舌镶。為了保證本教程足夠簡單,并聚焦與學(xué)習(xí)如何使用 Entity Framework 技術(shù)豪娜, 我們將不使用倉儲模式餐胀。 有關(guān)如何配合 EF 使用倉儲模式, 請參見 本教材最后一個章節(jié)瘤载。
自定義 Detail 頁
學(xué)生 Index 頁面的腳手架代碼沒有使用到 Enrollment
屬性否灾,這個屬性指向一個集合。 在 Detail 頁面鸣奔,您將在 HTML 表格中顯示該集合的內(nèi)容墨技。
在 Controllers/StudentsController.cs
中, Detail
視圖對應(yīng)的 Action 方法使用 SingleOrDefaultAsync
方法來檢索單個 Student
實體挎狸。 添加調(diào)用 Include, ThenInclude 和 AsNoTracking 方法的代碼扣汪。 如下面的代碼所示。
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}
return View(student);
}
Include
和 ThenInclude
方法讓 _context (數(shù)據(jù)庫上下文) 同時加載Student.Enrollments
導(dǎo)航屬性锨匆,并對每個 Enrollment
加載對應(yīng)的 Enrollment.Course
導(dǎo)航屬性崭别。 您將在 六、讀取相關(guān)數(shù)據(jù) 章節(jié)中了解有關(guān)這些方法的更多信息恐锣。
AsNoTracking
方法在當(dāng)前上下文生命周期中返回的實體不會更新的情況下有助于提高性能茅主。 您將在本章節(jié)末尾詳細(xì)了解 AsNoTracking
。
MVC 路由
傳遞給 Details
方法的鍵值來自路由數(shù)據(jù)侥蒙。 路由數(shù)據(jù)是模型綁定器在 URL 字符串中找到的數(shù)據(jù)暗膜。 例如,默認(rèn)路由指定 controller鞭衩,action 和 id :
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
在以下URL中学搜,默認(rèn)路由將 Instructor
作為控制器娃善,將 Index
作為操作,將 1
作為 id ; 這些就是路由數(shù)據(jù)值瑞佩。
http://localhost:1230/Instructor/Index/1?courseID=2021
URL 的最后一部分 "?courseID=2021"
是一個查詢字符串值聚磺。 如果將其作為查詢字符串值傳遞,模型綁定將會將 ID 值傳遞給 Details 方法的 id 參數(shù):
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
在 Index 頁面中炬丸,Razor 引擎的 Tag 幫助輔助聲明負(fù)責(zé)創(chuàng)建超鏈接瘫寝。在以下 Razor 代碼中,id 參數(shù)與默認(rèn)路由匹配稠炬,因此 id 被添加到路由數(shù)據(jù)中焕阿。
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>
當(dāng)上面代碼中的 item.ID 值為 6,生成如下HTML:
<a href="/Students/Edit/6">Edit</a>
有關(guān) Tag Helpers 的更多信息首启,請參閱 Tag helpers in ASP.NET Core 暮屡。
在 Detail 視圖中添加 Enrollments 實體信息
打開 Views/Students/Details.cshtml 。 如下所示毅桃,可以看到褒纲,每個字段使用 DisplayNameFor
和 DisplayFor
幫助方法來顯示:
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>
在最后一個字段之后,并在 </dl>
標(biāo)記之前钥飞,添加以下代碼以顯示 Enrollment 列表:
<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>
粘貼代碼后莺掠,如果代碼縮進(jìn)錯誤,請按 CTRL-K-D 進(jìn)行更正读宙。
這段代碼循環(huán)讀取 Enrollments
導(dǎo)航屬性中的實體彻秆。 對于每個 Enrollment 實體,顯示 Course Title
(課程標(biāo)題)和 Grade
(成績)论悴。 課程標(biāo)題從存儲在 Enrollment
實體的 Course
導(dǎo)航屬性中的 Course
實體檢索掖棉。
運(yùn)行應(yīng)用程序墓律,點擊 Student 鏈接 膀估,然后單擊任意學(xué)生的詳細(xì)信息鏈接。 您會看到所選學(xué)生的課程和成績列表:
更新 Create
頁面
在 StudentsController.cs
中耻讽,修改 HttpPost Create
方法察纯,添加一個try-catch 代碼塊,并從 Bind
特性中移除 ID
针肥。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and 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 Model Binder (模型綁定器)創(chuàng)建的 Student
實體添加到 Students
實體集饼记, 然后保存更改至數(shù)據(jù)庫。模型綁定器是 ASP.NET MVC 功能慰枕,有助于更容易處理表單提交的數(shù)據(jù);模型綁定器將發(fā)布的表單值轉(zhuǎn)換為 CLR 類型具则,并將它們傳遞給參數(shù)中的action 方法,在這種情況下具帮, 模型綁定器使用 Form 集合中的屬性值為您實例化 Student
實體博肋。
從 Bind
特性中刪除了 ID
低斋,因為 ID
是主鍵,SQL Server 會在添加數(shù)據(jù)行時自動設(shè)置匪凡。 來自用戶的輸入不設(shè)置 ID
值膊畴。
除了 Bind
特性外,try-catch
代碼塊是您對腳手架代碼進(jìn)行的唯一更改病游。 如果在保存更改時捕獲來自 DbUpdateException
的異常唇跨,則會顯示通用錯誤消息。 DbUpdateException
異常有時由應(yīng)用程序外部的東西引起衬衬,而不是編程錯誤买猖,因此建議用戶再次嘗試。 雖然在此示例中未實現(xiàn)滋尉,但生產(chǎn)質(zhì)量應(yīng)用程序?qū)⒂涗洰惓政勃!?有關(guān)更多信息,請參閱 Monitoring and Telemetry (Building Real-World Cloud Apps with Azure) 的 Log for insight 部分兼砖。
ValidateAntiForgeryToken
特性用于防止跨域請求偽造(CSRF)攻擊奸远。 令牌由 FormTagHelper
自動注入視圖,并在用戶提交表單時包含該令牌讽挟。 該令牌由 ValidateAntiForgeryToken
特性驗證懒叛。 有關(guān) CSRF 的更多信息,請參閱 Anti-Request Forgery 耽梅。
Over-Posting (過多提交) 安全提示
腳手架代碼在 Create
方法中包含的 Bind
特性是在創(chuàng)建場景中防止出現(xiàn) Over-Posting
(過多提交)的一種方法薛窥。 例如,假設(shè)學(xué)生實體中包含您不希望此網(wǎng)頁可以設(shè)置的 Secret
屬性眼姐。
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; }
}
即使您沒有網(wǎng)頁上的 Secret
字段诅迷,黑客也可以使用諸如 Fiddler 之類的工具,或者寫一些 JavaScript 來發(fā)布一個 Secret
表單值众旗。 如果沒有 Binder 特性限制模型綁定器創(chuàng)建Student
實例時可以使用的字段罢杉,模型綁定器將取得該 Secret
表單值,并用它來創(chuàng)建 Student
實體實例贡歧。 那么無論什么值滩租,黑客為秘密表單字段指定的數(shù)據(jù)將在數(shù)據(jù)庫中更新。 以下圖像顯示了 Fiddler 工具將 Secret 字段(值為 “OverPost”
)添加到發(fā)布的表單值利朵。
然后律想,OverPost
值將成功添加到插入行的 Secret
屬性,盡管您從未打算讓網(wǎng)頁能夠設(shè)置該屬性绍弟。
在編輯場景技即,您可以通過先從數(shù)據(jù)庫讀取實體,然后調(diào)用 TryUpdateModel
樟遣,傳遞一個明確允許的屬性列表而叼,從而防止編輯場景中的 Over-Posting 攻擊郭脂。 這也是本教程中使用的方法。
開發(fā)人員喜歡使用的另外一個防止 Over-Posting 攻擊的方法是使用 ViewModel
(視圖模型)澈歉,而不是直接綁定數(shù)據(jù)實體展鸡。 在 ViewModel
中僅包含需要更新的屬性。 一旦 MVC 模型綁定完成埃难,復(fù)制 ViewModel
屬性到數(shù)據(jù)實體莹弊,可選擇使用 AutoMapper
工具。 在實體實例上使用 _context.Entry
將其狀態(tài)設(shè)置為 Unchanged
涡尘,然后在 ViewModel
中包含的每個實體屬性上設(shè)置 Property("PropertyName")
的 IsModified
為 true
忍弛。 此方法可用于編輯和創(chuàng)建場景。
測試 Create
頁面
Views/Students/Create.cshtml
的代碼中考抄,字段使用了 label
, Input
, span
(用于展示驗證消息) 等標(biāo)簽創(chuàng)建细疚。
運(yùn)行應(yīng)用程序,點擊 Student
鏈接川梅,然后點擊 Create
疯兼。
輸入名稱和日期。 嘗試輸入無效的日期贫途,如果您的瀏覽器允許您這樣做吧彪。 (某些瀏覽器強(qiáng)制您使用日期選擇器。)然后單擊 Create
以查看錯誤消息丢早。
這是您默認(rèn)獲得的服務(wù)器端驗證; 在后面的教程中姨裸,您將看到如何添加可以生成客戶端驗證代碼的特性。 以下代碼顯示了 Create
方法中的模型驗證檢查怨酝。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}
將日期更改為有效值傀缩,然后單擊 Create
,將會看到這個新學(xué)生出現(xiàn)在 Index
頁面中农猬。
更新 Edit
頁面
在 StudentController.cs
中赡艰,HttpGet Edit
方法(沒有 HttpPost
特性的那個方法)使用 SingleOrDefaultAsync
方法來檢索所選的 Student 實體,就像在 Details
方法中看到的那樣盛险。 您不需要更改此方法瞄摊。
筆者建議的 HttpPost Edit
代碼: 先讀后更新
使用以下代碼替換 HttpPost Edit Action 方法勋又。
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.SingleOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(studentToUpdate);
}
代碼更改中苦掘,包含了防止 Over-Posting 攻擊的安全最佳實踐。 腳手架生成一個 Bind
特性楔壤,并將 model binder (模型綁定器) 創(chuàng)建的實體標(biāo)記為 Modified
(數(shù)據(jù)被修改)鹤啡。 在許多場景中,不推薦使用那種代碼蹲嚣,因為 Bind
特性會清除所有未在 Include
參數(shù)中出現(xiàn)的字段值递瑰。
新實現(xiàn)的代碼先讀取現(xiàn)有實體祟牲,然后按照提交表單數(shù)據(jù)中的用戶輸入調(diào)用 TryUpdateModel
來更新檢索到的實體中的字段。 Entity Framework 自動跟蹤機(jī)制在被修改的字段上打 Modified
標(biāo)記抖部,在調(diào)用 SaveChanges
方法時说贝, Entity Framework 創(chuàng)建 SQL 語句來更新數(shù)據(jù)庫行。并非沖突將被忽略慎颗,只有只有用戶更新的數(shù)據(jù)表字段在數(shù)據(jù)庫中被更新乡恕。(稍后的教程將展示如何處理并發(fā)沖突。)
防止 Over-Posting 攻擊的最佳做法中俯萎,你希望被更新的字段作為 TryUpdateModel
參數(shù)列入白名單傲宜,參數(shù)列表中字段列表之前的空字符串用于表單字段名稱的前綴。目前沒有要保護(hù)的額外字段夫啊,但列出了要綁定的字段函卒,這樣可以確保如果將來在數(shù)據(jù)模型添加新的字段,它們將被自動保護(hù)撇眯,直到您在此處顯式添加报嵌。
這樣修改之后,HttpPost Edit
和 HttpGet Edit
兩個方法的方法簽名完全相同(輸入和輸出類型相同)熊榛,所以你可以看到沪蓬,在代碼中我們修改方法名為 EditPost
。
可替代的 HttpPost Edit
代碼来候,創(chuàng)建然后附加
推薦的 HttpPost Edit
代碼可確保僅更改的列被更新跷叉,并保留不需要包含的模型綁定的屬性中的數(shù)據(jù)。然而营搅,先讀取的方法需要額外的數(shù)據(jù)庫讀取云挟,并且可能導(dǎo)致更復(fù)雜的并發(fā)沖突處理代碼。另一種方法是將由模型綁定器創(chuàng)建的實體附加到EF上下文并將其標(biāo)記為已修改转质。 (不要使用此代碼更新項目园欣,此處只是演示一種可選的方法。)
public async Task<IActionResult> Edit(int id, [Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
if (id != student.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(student);
}
僅當(dāng)網(wǎng)頁界面包含實體中的所有字段并且可以更新其中任何一個字段時休蟹,您可以使用此方法沸枯。
腳手架代碼使用 create-and-attach 方法,但僅捕獲 DbUpdateConcurrencyException
并發(fā)沖突異常并返回 404 錯誤代碼赂弓。 修改后的代碼捕獲任何數(shù)據(jù)庫更新異常并顯示錯誤消息绑榴。
實體狀態(tài) (Entity States)
數(shù)據(jù)庫上下文跟蹤內(nèi)存中的實體是否與數(shù)據(jù)庫中的相應(yīng)行同步,并使用此信息決定當(dāng)用戶調(diào)用 SaveChanges
方法時會發(fā)生什么盈魁。 例如翔怎,當(dāng)您將新實體傳遞給 Add
方法時,該實體的狀態(tài)將設(shè)置為 Added
。 然后當(dāng)調(diào)用 SaveChanges
方法時赤套,數(shù)據(jù)庫上下文會發(fā)出 SQL INSERT
命令飘痛。
實體可能處于以下狀態(tài)之一:
-
Added
: 實體在數(shù)據(jù)庫中不存在。SaveChanges
方法發(fā)出一個INSERT
語句容握。 -
Unchanged
:SaveChanges
方法不需要對此實體做任何操作宣脉。 當(dāng)您從數(shù)據(jù)庫中讀取實體時,實體將以此狀態(tài)啟動剔氏。 -
Modified
:實體的某些或全部屬性值已被修改脖旱。SaveChanges
方法發(fā)出UPDATE
語句。 -
Deleted
:實體被標(biāo)記為已刪除介蛉。SaveChanges
方法發(fā)出DELETE
語句萌庆。 -
Detached
: 實體沒有被數(shù)據(jù)庫上下文跟蹤。
在桌面應(yīng)用程序中币旧,實體狀態(tài)通常會自動被設(shè)置践险。你讀取一個實體并對其某些屬性值進(jìn)行更改,這將導(dǎo)致其實體狀態(tài)自動更改為 Modified
吹菱,然后你調(diào)用 SaveChanges
巍虫,Entity Framework 生成 SQL 更新語句,該語句僅更新您實際更改的屬性鳍刷。
而在 Web 應(yīng)用程序中占遥,讀取一個實體并顯示于頁面用于編輯的數(shù)據(jù)庫上下文在頁面渲染后將被銷毀(請記住,Web是無狀態(tài)的 Stateless)输瓜。 當(dāng)調(diào)用 HttpPost Edit
操作方法時瓦胎,會創(chuàng)建一個新的 Web 請求,并開啟一個新的 DbContext 實例尤揣。 如果您在新的上下文中重讀了實體搔啊,則可以模擬桌面處理。
但是北戏,如果您不想執(zhí)行額外的讀取操作负芋,您將不得不使用由模型綁定器創(chuàng)建的實體對象。 執(zhí)行此操作的最簡單的方法是將實體狀態(tài)設(shè)置為 Modified
嗜愈,就像之前顯示的其中一種 HttpPost Edit
代碼一樣旧蛾。 然后當(dāng)您調(diào)用 SaveChanges
時,Entity Framework 更新數(shù)據(jù)庫行的所有列蠕嫁,因為上下文無法知道您更改了哪些屬性锨天。
如果您想避免 read-first
方法,但您同時希望 SQL UPDATE
語句僅更新用戶實際更改的字段拌阴,代碼將會更加復(fù)雜绍绘。您必須以某種方式保存原始值(例如使用隱藏字段),以便在調(diào)用 HttpPost Edit
方法時可用迟赃。 然后陪拘,您可以使用原始值創(chuàng)建一個 Student
實體,使用該實體的原始版本調(diào)用 Attach
方法纤壁,將實體的值更新為新值左刽,然后調(diào)用 SaveChanges
。
測試 Edit
頁面
運(yùn)行應(yīng)用程序酌媒,點擊 Student 鏈接 欠痴,然后單擊 Edit
超鏈接。
更改一些數(shù)據(jù)秒咨,然后單擊保存喇辽。 保存后跳轉(zhuǎn)至 Index 頁面晾捏,您將看到數(shù)據(jù)已被更改枯冈。
更新 Delete
頁面
在 StudentController.cs 中嚎卫,HttpGet Delete
方法的模板代碼使用 SingleOrDefaultAsync
方法來檢索所選的 Student
實體评姨,就像在 Details
和 Edit
方法中看到的一樣补履。但為了在調(diào)用 SaveChanges
方法是實現(xiàn)自定義錯誤信息导披,您將向此方法及其對應(yīng)視圖添加一些功能播瞳。
正如你在 Update
和 Create
操作中看到的理肺,刪除操作需要兩個 Action
方法糙置。一個用于響應(yīng) GET
請求云茸,顯示一個視圖,給用戶批準(zhǔn)或取消刪除操作的機(jī)會谤饭。一旦用戶同意刪除标捺,一個 POST
請求被創(chuàng)建,當(dāng)發(fā)生這種情況時揉抵,調(diào)用 HttpPost Delete
方法宜岛,然后該方法執(zhí)行實際的刪除操作。
您將向 HttpPost Delete
方法添加一個 try-catch
代碼塊功舀,以處理數(shù)據(jù)庫更新時可能發(fā)生的任何錯誤萍倡。 如果發(fā)生錯誤,HttpPost Delete
方法調(diào)用 HttpGet Delete
方法辟汰,傳遞一個指示發(fā)生錯誤的參數(shù)列敲。 HttpGet Delete
方法重新顯示確認(rèn)頁面以及錯誤消息,給予用戶取消或再次嘗試的機(jī)會帖汞。
將 HttpGet Delete
操作方法替換為以下實現(xiàn)了錯誤報告機(jī)制的代碼戴而。
public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"Delete failed. Try again, and if the problem persists " +
"see your system administrator.";
}
return View(student);
}
這段代碼接受一個可選參數(shù),指示方法是否在保存更改失敗后被調(diào)用翩蘸。當(dāng) HttpGet Delete
方法被調(diào)用前未有保存失敗的情況發(fā)生所意,此參數(shù)為 false
。 當(dāng) HttpPost Delete
方法響應(yīng)數(shù)據(jù)庫更新錯誤調(diào)用該參數(shù)時,該參數(shù)為 true
扶踊,一個錯誤消息被傳遞給該視圖泄鹏。
HttpPost Delete
的 read-first
方法
使用以下代碼替換 HttpPost Delete
操作方法(修改命名為 DeleteConfirmed
),該代碼執(zhí)行實際的刪除操作并捕獲任何數(shù)據(jù)庫更新錯誤秧耗。
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return RedirectToAction(nameof(Index));
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}
此代碼檢索所選實體备籽,然后調(diào)用 Remove
方法將實體的狀態(tài)設(shè)置為 Deleted
。 調(diào)用 SaveChanges
時分井,將生成一個 SQL DELETE
命令车猬。
HttpPost Delete 的 create-and-attach 方法
假如在大量數(shù)據(jù)處理應(yīng)用中需要優(yōu)先提升性能,可以通過僅使用主鍵值實例化 Student
實體尺锚,然后將實體狀態(tài)設(shè)置為 Deleted
來避免不必要的SQL查詢珠闰。 Entity Framework 刪除一個實體可以僅需如此。(不要把這段代碼放在你的項目中瘫辩,這里只是為了說明您可以有多種方式處理刪除)
譯者注:許多童鞋批評 Entity Framework 無謂的 read-first 消耗數(shù)據(jù)庫性能伏嗜,其實并不是如此,每一種策略都有其背后的深思熟慮杭朱。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}
如果實體有相關(guān)數(shù)據(jù)也應(yīng)該被刪除阅仔,請確保在數(shù)據(jù)庫中配置了級聯(lián)刪除。 通過這種實體刪除方法弧械,EF 可能不會意識到有相關(guān)的實體被刪除八酒。
更新 Delete 視圖
在 Views/Student/Delete.cshtml 中,在 h2 標(biāo)題和 h3 標(biāo)題之間添加錯誤消息刃唐,如下所示:
<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>
運(yùn)行應(yīng)用程序羞迷,點擊 Student 鏈接 ,然后單擊一個 Delete
超鏈接画饥。
點擊 Delete 按鈕衔瓮, 將會跳轉(zhuǎn)至 Index 頁面,可以見到抖甘,剛剛刪除的學(xué)生已經(jīng)不在顯示列表中热鞍。(在 八、處理并發(fā)沖突 中將看到錯誤處理代碼的示例衔彻。)
關(guān)閉數(shù)據(jù)庫連接
為了釋放數(shù)據(jù)庫連接所擁有的資源薇宠,上下文實例必須在完成后盡快處理。 ASP.NET Core 依賴注入機(jī)制為您處理該任務(wù)艰额。
在 Startup.cs 中澄港,您調(diào)用 AddDbContext 擴(kuò)展方法在 ASP.NET DI 容器中配置 DbContext 類。 該方法默認(rèn)將服務(wù)生命周期設(shè)置為 Scoped 柄沮。 Scoped 意味著上下文對象的生命周期與 Web 請求的使用壽命一致回梧,Dispose 方法將在 Web 請求結(jié)束時自動調(diào)用废岂。
事務(wù)處理
默認(rèn)情況下,EF 隱式實現(xiàn)事務(wù)狱意。 在對多個行或表進(jìn)行更改湖苞,然后調(diào)用 SaveChanges 的情況下,EF 將自動確保所有更改都成功或全部失敗髓涯。 如果有一些更改完成袒啼,然后發(fā)生錯誤哈扮,那么這些更改將自動回滾纬纪。 對于需要更多控制的場景,例如滑肉,如果要在事務(wù)中包含 Entity Framework 以外的操作 - 請參閱 Transactions包各。
無追蹤查詢
當(dāng)數(shù)據(jù)庫上下文檢索表行并創(chuàng)建表示它們的實體對象時,默認(rèn)情況下會跟蹤內(nèi)存中的實體是否與數(shù)據(jù)庫中的實體同步靶庙。 內(nèi)存中的數(shù)據(jù)充當(dāng)緩存,并在更新實體時使用六荒。 這種緩存在 Web 應(yīng)用程序中通常是不必要的护姆,因為上下文實例通常是短暫的(為每個請求創(chuàng)建一個新的實例)卵皂,并且通常在再次使用該實體之前設(shè)置讀取實體的上下文捅膘。
您可以通過調(diào)用 AsNoTracking 方法來禁用對內(nèi)存中實體對象的跟蹤。 您可能希望這樣做的典型場景包括:
- 在上下文生存期間,您不需要更新任何實體,并且您不需要 EF 自動加載導(dǎo)航屬性禾酱,而實體由單獨(dú)的查詢檢索涛漂。 這些條件通常在控制器的HttpGet操作方法中得到滿足距潘。
- 您正在運(yùn)行一個檢索大量數(shù)據(jù)的查詢,只有一小部分返回的數(shù)據(jù)將被更新。 關(guān)閉大型查詢的跟蹤可能會更有效俱笛,并且稍后為需要更新的幾個實體運(yùn)行查詢捆姜。
- 您想要附加一個實體來更新它,但是之前您為了不同的目的檢索了同一個實體嫂粟。 因為實體已經(jīng)被數(shù)據(jù)庫上下文跟蹤娇未,所以您不能附加要更改的實體墨缘。 處理這種情況的一種方法是在較早的查詢中調(diào)用 AsNoTracking星虹。
如欲了解更多有關(guān)信息,請參閱 Tracking vs. No-Tracking.
小結(jié)
您現(xiàn)在有一套完整的頁面可以為 Student 實體執(zhí)行簡單的 CRUD 操作镊讼。 在下一個教程中宽涌,您將通過添加排序,篩選和分頁來擴(kuò)展 Index 頁的功能蝶棋。