ASP.NET Core MVC 和 Entity Framework Core 入門教程 - CRUD(二)

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é)瘤载。

Student Detail
Create
Edit
Delete

自定義 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);
}

IncludeThenInclude 方法讓 _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 。 如下所示毅桃,可以看到褒纲,每個字段使用 DisplayNameForDisplayFor 幫助方法來顯示:

<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é)生的課程和成績列表:

StudentDetail

更新 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ā)布的表單值利朵。

fiddler

然后律想,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")IsModifiedtrue 忍弛。 此方法可用于編輯和創(chuàng)建場景。

測試 Create 頁面

Views/Students/Create.cshtml 的代碼中考抄,字段使用了 label, Input, span (用于展示驗證消息) 等標(biāo)簽創(chuàng)建细疚。
運(yùn)行應(yīng)用程序,點擊 Student 鏈接川梅,然后點擊 Create疯兼。
輸入名稱和日期。 嘗試輸入無效的日期贫途,如果您的瀏覽器允許您這樣做吧彪。 (某些瀏覽器強(qiáng)制您使用日期選擇器。)然后單擊 Create 以查看錯誤消息丢早。

create error

這是您默認(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 EditHttpGet 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 語句容握。
  • UnchangedSaveChanges 方法不需要對此實體做任何操作宣脉。 當(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 超鏈接。

edit

更改一些數(shù)據(jù)秒咨,然后單擊保存喇辽。 保存后跳轉(zhuǎn)至 Index 頁面晾捏,您將看到數(shù)據(jù)已被更改枯冈。

更新 Delete 頁面

在 StudentController.cs 中嚎卫,HttpGet Delete 方法的模板代碼使用 SingleOrDefaultAsync 方法來檢索所選的 Student 實體评姨,就像在 DetailsEdit 方法中看到的一樣补履。但為了在調(diào)用 SaveChanges 方法是實現(xiàn)自定義錯誤信息导披,您將向此方法及其對應(yīng)視圖添加一些功能播瞳。
正如你在 UpdateCreate 操作中看到的理肺,刪除操作需要兩個 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 Deleteread-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

點擊 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 頁的功能蝶棋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卸亮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子玩裙,更是在濱河造成了極大的恐慌兼贸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吃溅,死亡現(xiàn)場離奇詭異溶诞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)决侈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門螺垢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赖歌,你說我怎么就攤上這事枉圃。” “怎么了庐冯?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵孽亲,是天一觀的道長。 經(jīng)常有香客問我展父,道長返劲,這世上最難降的妖魔是什么赁酝? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮旭等,結(jié)果婚禮上酌呆,老公的妹妹穿的比我還像新娘。我一直安慰自己搔耕,他們只是感情好隙袁,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弃榨,像睡著了一般菩收。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲸睛,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天娜饵,我揣著相機(jī)與錄音,去河邊找鬼官辈。 笑死箱舞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拳亿。 我是一名探鬼主播晴股,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肺魁!你這毒婦竟也來了电湘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鹅经,失蹤者是張志新(化名)和其女友劉穎寂呛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘾晃,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贷痪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酗捌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呢诬。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胖缤,靈堂內(nèi)的尸體忽然破棺而出尚镰,到底是詐尸還是另有隱情,我是刑警寧澤哪廓,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布狗唉,位于F島的核電站,受9級特大地震影響涡真,放射性物質(zhì)發(fā)生泄漏分俯。R本人自食惡果不足惜肾筐,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缸剪。 院中可真熱鬧吗铐,春花似錦、人聲如沸杏节。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奋渔。三九已至镊逝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫉鲸,已是汗流浹背撑蒜。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留玄渗,地道東北人座菠。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像捻爷,于是被迫代替她去往敵國和親辈灼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容