您的位置:首页 > 编程语言 > ASP

增、查、改、删 -- EF Core 与 ASP.NET Core MVC 教程(2 of 10)

2017-07-28 17:17 1116 查看
By Tom Dykstra and Rick Anderson

Contoso University 简单 web 应用程序演示了如何使用 Visual Studio 2017 和 Entity
Framework Core 1.1 创建 ASP.NET Core 1.1 MVC web 应用程序。相关教程信息,请查看 the first tutorial in the series(系列教程第一部分).

在前一部分的教程中,使用了 Entity Framework 和 SQL Server LocalDB 创建一个 MVC
应用程序用于存储和显示数据。在本教程中,你将查看并自定义 MVC 基架在控制器和视图中为你自动创建的 CRUD (create, read,
update, delete)代码。


[!注意]

实现存储模式是一种常见的作法,以在控制器和 DAL(data access layer
数据访问层)之间创建一个抽象层。为了保持本教程的简单化并专注于如何使用 Entity Framework 本身,这里并不使用存储库。关于 EF
中使用存储库的更多信息,请浏览 the last tutorial in this series


在本教程中,你将使用以下的网页:

















定制详细信息页

由基架为 Students Index 页生成的代码遗漏了
Enrollments
属性,因为这个属性保存的内容是一个集合。在 Details 页,你将在 HTML 表格中显示这个集合中的内容。

在 Controllers/StudentsController.cs 中,Details 视图的操作方法使用
SingleOrDefaultAsync
方法去检索单个
Student
实体。添加调用
Include
代码。
ThenInclude
, 和
AsNoTracking
方法,如以下显示的代码所示。

[!code-csharpMain(StudentsController完整代码)]

#region snippet_Details
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);
}
#endregion


Include
ThenInclude
方法导致上下文加载
Student.Enrollments
导航属性,以及在每个 enrollment 中的
Enrollment.Course
导航属性。您将在 reading related data(读取相关数据)教程中了解有关这些方法的更多信息。

AsNoTracking
方法能够提高在当前上下文的生命周期内返回的实体不被更新的情况下的性能。在本教程的结尾部分,你将会学习更多关于
AsNoTracking
的内容。

路由数据

传送给
Details
方法的键值来自 route data(路由数据)。路由数据模型绑定器在URL段中找到的数据。例如,默认路由指定控制器、动作和id段。

[!code-csharpMain]

#region snippet_RouteAndSeed
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

DbInitializer.Initialize(context);
#endregion


I在下面的 URL,默认路由将 Instructor 映射为控制器,Index 映射为一个操作方法,1 映射为id(的值);这些就是路由数据值。

http://localhost:1230/Instructor/Index/1?courseID=2021

URL 的最后一部分("?courseID=2021") 是一个查询字符串值。如果将其做为查询字符值进行传递,模型绑定器也将 ID 的值传递到
Details
方法的
id
参数中。

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

I在 Index 页中,超链接 URL 是由 Razor 视图的 tag helper 语句创建。 在以下的 Razor 代码中,
id
参数匹配默认路由,所以
id
会被添加到路由数据中。

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>


item.ID
的值为6时,将产生以下的 HTML 代码:

<a href="/Students/Edit/6">Edit</a>


在以下的 Razor 代码中,
studentID
不匹配默认路由中的参数,所以它会被做为查询字符串添加。

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>


item.ID
的值为6时,将产生以下的 HTML 代码:

<a href="/Students/Edit?studentID=6">Edit</a>


更多关于 about tag helpers 的信息,请浏览 Tag helpers in ASP.NET Core.

添加在校人数到详细信息视图

打开 Views/Students/Details.cshtml 文件,每个字段都使用
DisplayNameFor
DisplayFor
帮助器显示,如下面的例子所示:

[!code-htmlDetails]

@model ContosoUniversity.Models.Student

@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Student</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.FirstMidName)
</dt>
<dd>
@Html.DisplayFor(model => model.FirstMidName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</dt>
<dd>
@Html.DisplayFor(model => model.EnrollmentDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.ID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>


在最后一个字段的后面,紧接在关闭 "" 标记之前,添加下面的代码去显示一个在校人数列表:

[!code-htmlDetails]

@model ContosoUniversity.Models.Student

@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Student</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.FirstMidName)
</dt>
<dd>
@Html.DisplayFor(model => model.FirstMidName)
</dd>
<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>
<div>
<a asp-action="Edit" asp-route-id="@Model.ID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>


如果在粘贴代码后出现代码缩进错误, 请按 CTRL-K-D 进行更正。

这段代码将循环遍历
Enrollments
导航属性中的实体,对于每个 enrollment,都显示它的课程标题和成绩。课程标题都是从存储在 Enrollments 实体的 Course 导航属性中的 Course 实体中检索出来的。

运行应用程序,选择 Students 标签,然后单击 Details 中的一个学生链接。就可以看到被选中学生的课程和成绩列表:





修改 Create(增加)页

在 StudentsController.cs 文件中,修改 HttpPost 方式的
Create
方法,添加一个 try-catch 块结构并从
Bind
特性中删除 ID。

[!code-csharpMain]

// POST: Students/Create
#region snippet_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("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);
}
#endregion


这段代码将由 ASP.NET MVC 模型绑定器创建的 Student 实体添加到 Students 实体集,然后将更改可在到数据库中。(模型绑定器引用 ASP.NET MVC 功能,可以更轻松处理从表单提交的数据;模型绑定器将发布的表单值转换为CLR类型,并将它们传递给参数中的操作方法。在这种情况下,模型绑定器使用 Form 集合中的属性值实例化一个 Student 实体。)

Bind
特性中删除
ID
是因为
ID
是 任何一个 SQL Server 插入行时都会自动添加的主键值。而来看成用户的输入并没有提供 ID 值。

除了
Bind
特性之外,try-catch 块结构是对由基架生成的代码的唯一更改。如果在保存更新时捕获来自
DbUpdateException
的异常,则会显示一个通用的错误信息。
DbUpdateException
异常是有时候是由应用程序外部的某些原因引起的而不是编程错误,因此建议用户再次尝试。虽然此示例中未实现, 但生产质量应用程序将记录该异常。有关更多的信息,请在 Monitoring and Telemetry (Building Real-World Cloud Apps with Azure) 上浏览有 Log for insight 部分的内容。

ValidateAntiForgeryToken
特性有助于防止跨站点请求伪造(CSRF)攻击。这个 token(令牌)是 FormTagHelper 自动注入视图,并在用户提交表单时包含该令牌。 令牌由
ValidateAntiForgeryToken
特性验证。更多有关 CSRF 的令牌,请浏览 🔧 Anti-Request Forgery.

关于 overposting(过度提交) 的安全说明

基架代码包含在
Create
方法中的
Bind
特性是在创建方案中防止过度提交的一种方法。例如,假设 Student 实体中包含一个你不希望在这个网页中设置的
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; }
}


即使你的的网页上没有
Secret
字段,黑客也可以使用诸如 Fiddler 之类的工具,或者编写一段 JavaScript代码,去提交一个
Secret
表单值。如果不使用
Bind
特性限制模型绑定器创建 Student 实例时使用的字段,模型绑定器将获取
Secret
表单值,并使用它去创建 Student 实体实例。然后,无论黑客为
Secret
表单字段指定的是什么值都会被更新到数据库中。下图显示了 Fiddler 工具将
Secret
字段(字段的值为 "OverPost") 添加到已提交的表单值中。





然后,值 "OverPost" 将被成功地添加到插入行的
Secret
属性中,尽管你从未打算在网页中设置该属性。

你可以先从数据库中读取实体,然后再调用
TryUpdateModel
来阻止编辑场景中的 overposting (过度提交)。传入一个显示允许(白名单)的属性列表。这是在本教程中使用的方法。

防止 overposting (过度提交)的另一种办法是使用视图模型而不是具有模型绑定的实体类。仅包含在视图模型中需要更新的属性。 MVC 模型绑定器完成后,将视图模型属性复制到实体实例中,可以使用诸如 AutoMapper 之类的工具。在实体实例上使用
_context.Entry
,将其状态设置为
Unchanged
,然后在视图模型中包含的每个实体属性上设置
Property("PropertyName").IsModified
设置为 true。这个方法适用于编辑和创建场景。

测试 Create 页

对 Views/Students/Create.cshtml 文件中的每一个字段使用
label
,
input
, 和
span
(用于验证消息) 标记辅助代码。

通过选择 Students 标签并单击 Create New 运行这个页面。

输入名字 和一个无效的日期然后单击 Create 以查看错误信息。





这是默认情况下得到的服务器端验证;在后面的教程,你将看到如何添加为客户端验证生成代码的特性。以下突出显示的代码显示了在
Create
方法中的模型验证检查。

[!code-csharpMain]

// POST: Students/Create
#region snippet_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("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);
}
#endregion


把日期更改为一个有效的值然后单击 Create 查看出现在 Index 页面的新 student 。

更新 Edit 页

在 StudentController.cs 文件中, HttpGet 方式的
Edit
方法(没有
HttpPost
特性的那一个)使用
SingleOrDefaultAsync
方法来检索已经选定的 Student 实体,就象你在
Details
方法中看到的那样。你不需要修改这个方法。

推荐 HttpPost Edit 代码:读取和更新

使用以下的代码替换 HttpPost Edit 操作方法的内容。

[!code-csharpMain]

#elif (ReadFirst)
#region snippet_ReadFirst
[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("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);
}
#endregion
#endif


这些更改实现国安全最佳做法以防止过度提交。基架生成了一个
Bind
特性并添加模型绑定器创建的实体到
Modified
标志的实体集中。在很多情况下都不推荐使用这些代码,因为
Bind
特性清除了
Include
参数中未列出来的字段中的任何预先存在的代码。

新代码读取现在实体并调用
TryUpdateModel
去更新检索实体 based on user input in the posted form data中的字段。 Entity Framework的自动更改跟踪在通过表单输入更改的字段上设置
Modified
标志。当
SaveChanges
方法被调用,Entity Framework 会创建 SQL 语句去更新数据库中的行。忽略并发冲突,并且在数据库中只更新由用户更新的表列(后面的教程将演示如何处理并发冲突)。

作为防止 overposting(过度提交) 的最佳做法, 你可以通过在 Edit 页面把要更新的字段加入到
TryUpdateModel
参数的白名单(参数列表中的字段列表之前的空字符串是用于表单字段名称的前缀)。当前没有要保护的额外字段,但是列出你希望模型绑定器绑定的字段以确保如果你在将来为数据模型添加字段时,它们会被自动保护直到你在这里显式添加它们为止。

作为这些更改的结果,HttpPost 方式的
Edit
方法的方法签名与 HttpGet 方式的
Edit
方法相同;所以你已经重命名了
EditPost
方法。

替代的 HttpPost Edit 代码:创建和附加

推荐的 HttpPost edit 代码可确保只有变了的列得到更新,并保留不想包含在模型绑定的属性中的数据。然而,读取优先方法需要额外的数据库读取,并会导致更复杂的代码来处理并发冲突。另一种方法是将由模型绑定器创建的实体附加到 EF 上下文并将其标记为已修改(不要用此代码更新你的项目,这里只是演示了一个可选的方法)。

[!code-csharpMain]

#if (CreateAndAttach)
#region snippet_CreateAndAttach
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("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);
}
#endregion
#elif (ReadFirst) #region snippet_ReadFirst [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("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); } #endregion #endif


当网页UI包含实体中的所有字段并且可以更新它们中的任意一个的时候,可以使用这个方法。

基架代码使用创建附加方法,但只捕获
DbUpdateConcurrencyException
异常并返回 404 错误代码。显示的示例会捕获任何数据库更新异常并显示错误信息。

实体状态

数据库上下文跟踪内存中的实体是否与数据库中相应的行同步,并且此信息确定当你调用
SaveChanges
方法时会发生什么。例如,当你传递一个新的实体到
Add
方法时,该实体的状态设置为
Added
。当调用
SaveChanges
方法时,数据库上下文则会发出一个 INSERT 的 SQL 命令。

实体可能处于下列状态之一:

Added
:该实体还不存在于数据库中。
SaveChanges
方法发出一个 INSERT 语句。

Unchanged
SaveChanges
方法不需要对该实体进行任何操作。当从数据库中读取一个实体时,就是从这个状态开始的。

Modified
:该实体中的部分或全部属性被修改。
SaveChanges
方法发出一个 UPDATE 语句。

Deleted
:该实体被标记为删除。
SaveChanges
方法发出一个 DELETE 语句。

Detached
:该实体没有被数据库上下文跟踪。

I在桌面应用程序中,状态改变通常是自动设置的。读取一个实体并改变其中的一些属性值,会导致该实体的状态自动更改为
Modified
。那么当你调用
SaveChanges
方法时, Entity Framework 会生成一个 UPDATE 的 SQL 语句,只更新你改变了的实际属性。

在 web 应用程序中, 最初读取一个实体并显示其要编辑的数据的
DbContext
在页面被渲染之后释放。当调用 HttpPost 方式的
Edit
操作方法时,将发出一个新的 web 请求并得到一个新的
DbContext
实例。如果在一个新的上下文中重新读取了实体,则可以模拟桌面应用程序的处理。

但如果你不想做额外的读取操作,就必须使用由模型绑定器创建的实体对象。最简单在方法是将实体状态修改为前面所示的替代的 HttpPost Edit 代码。当调用
SaveChanges
方法时,Entity Framework 将更新数据库行中的所有列,因为上下文没有办法知道你修改了哪些属性。

如果想避免读取优先方法,同时又希望 SQL UPDATE 语句只更新用户实际更改的字段,代码会更复杂。你必须以某种方式(例如使用隐藏字段)保存原始值,以便在调用 HttpPost
Edit
方法时他们是可用的。你可以使用原始值创建一个 Student 实体,调用该实体原始版本的
Attach
方法,将实体的值更新为新的值,然后调用
SaveChanges
方法。

测试 Edit 页

运行这个应用程序并且选择 Students 标签,然后单击 Edit 超链接。





修改一些数据并单击 Save。在打开的 Index 页面查看修改后的数据。

更新 Delete 页面

在 StudentController.cs 文件中,HttpGet
Delete
方法的模板代码使用
SingleOrDefaultAsync
方法检索选中的 Student 实体,就象是在 Details 和 Edit 方法中看到的那样。但是,当调用
SaveChanges
失败要实现一个自定义的错误信息时,你需要在该方法及其相应视图中添加一些功能。

跟你在 update 和 create 操作中看到的一样, delete 操作也需要两个操作方法。 响应 GET 请求时调用的方法显示一个视图,让用户选择批准或是取消 delete 操作。如果用户批准删除则创建一个 POST 请求。这个时候, HttpPost
Delete
方法将被调用并执行真正的删除操作。

在 HttpPost
Delete
方法中添加一个 try-catch 语句块处理在数据库更新时可能发生的任何错误。如果错误出再,HttpPost Delete 方法调用 HttpGet Delete 方法,同时传递一个指示错误发生的参数。HttpGet Delete 方法重新显示确认删除的页面以及相应的错误信息,让用户一个取消或再次尝试(删除)的机会。

替换 HttpGet
Delete
操作方法为以下代码,该代码用于管理错误报告。

[!code-csharpMain]

// GET: Students/Delete/5
#region snippet_DeleteGet
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);
}
#endregion


此代码接受一个可选的参数,该参数指示方法在保存更改失败后被调用。当 HttpGet
Delete
方法在没有之前的失败被调用时该参数的值为 false。 当 HttpPost
Delete
方法在响应一个数据库更新错误被调用时,该参数的值是 true 并同时传递一个错误信息到相应的视图。

HttpPost Delete 的 read-first 方法

替换 HttpPost
Delete
(名字为
DeleteConfirmed
)操作方法为以下代码,该代码执行实际的删除操作并捕获任何数据库更新错误。

[!code-csharpMain]

#region snippet_DeleteWithReadFirst
[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("Index");
}

try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
}
#endregion


该代码检索选中的实体,然后调用
Remove
方法将实体状态设为
Deleted
。当调用
SaveChanges
时则生成一个 SQL DELETE 命令(在数据库中进行删除操作。)。

HttpPost Delete 的 create-and-attach 方法

如果在一个高容量的应用程序中提升性能是优先考虑的事情,则可以通过仅使用主键来实例化 Student 实体,然后将实体的状态设为
Deleted
来避免不必要的 SQL 查询。这就是 Entity Framework 为了删除实体而需要的全部。(不要将此代码放入你的项目中;这里仅仅是为了说明有这一种替代方案)

[!code-csharpMain]

#region snippet_DeleteWithoutReadFirst
[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("Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("Delete", new { id = id, saveChangesError = true });
}
}
#endregion


如果该实体有相关的数据也要一起删除,请确保在数据库中配置了级联删除。通过这种方法删除实体时,EF可能不会意识到相关实体已经被删除掉了。

更新 Delete 视图

在 Views/Student/Delete.cshtml 文件中,添加一个错误信息到 h2 和 h3 标题之间,如以下例子所示:

[!code-htmldelete]

@model ContosoUniversity.Models.Student

@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>


通过选择 Students 标签并单击 Delete 超链接运行该网页:





单击 Delete, Index 页面显示没有被删除的学生。(在并发教程中, 您将看到操作中的错误处理代码的示例.)

关闭数据库连接

要释放数据库连接所保留的资源,必须在完成该操作后尽快释放上下文实例。ASP.NET Core 内置的 dependency injection(依赖注入)为你处理该任务。

在 Startup.cs 中调用 AddDbContext extension method 在 ASP.NET DI 容器中提供
DbContext
类。该方法默认情况下将服务生命周期设置为
Scoped
Scoped
意味着 上下文对象的生命周期与 web 请求的生命周期一致,并且在 web 请求结束的时候自动调用
Dispose
方法。

事务处理

默认情况下 Entity Framework 隐式实现事务。在对多个行或表进行修改然后调用
SaveChanges
的情况下,Entity Framework 自动确保所有的更改全部成功或全部失败。如果首先完成一些更改然后发生了错误,那么这些更改将自动回滚。对于需要更多控制的场景 -- 例如,如果要在事务中包含Entity Framework外部执行的操作 -- 请浏览 Transactions

无跟踪查询

当数据库上下文检索表行并创建表示它们的实体对象时,默认情况下会跟踪内存中的实体是否与数据库中的内容同步。内存中的数据充当缓存并在更新实体时使用。在 web 应用程序中这种缓存通常是不必要的,因为上下文通常是短暂的(为每一个请求创建并释放一个新的实例),而读取实体的上下文通常在再次使用该实体前被释放。

你可以通常调用
AsNoTracking
方法来禁用对内存中实体对象的跟踪。你可能希望这样做的典型场景包括:

在上下文的生命周期内你不需要更新任何实体,并且你不需要 EF automatically load navigation properties with entities retrieved by separate queries(使用单独查询检索的实体自动加载导航属性)。这些条件通常在控制器的 HttpGet 操作方法中得到满足。

正在运行一个检索大量数据的查询,只有一小部分返回的数据将被更新。关闭大型查询的跟踪可能会更有效,稍后对需要更新的少数实体运行查询。

要附加一个实体来更新它,但是之前您为了不同的目的检索了同一个实体。因为实体已被数据库上下文跟踪,所以您不能附加要更改的实体。处理这种情况的一种方法是在前面的查询中调用 "AsNoTracking"。

有关更多的信息,请浏览 Tracking vs. No-Tracking

小结

你现在有了一组完整的页面可以为 Student 实体执行简单的 CRUD 操作。在下一个教程中,你将通过添加 排序、过滤和分布来扩展 Index 页面的功能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: