在EFCore中的CRUD

在EFCore中的CRUD

在EFCore中的CRUD

实体的几种状态

  1. 一个实体可能会会有以下几种状态:

    • Added: 实体在数据库中不存在。SaveChanges 方法将发起 INSERT 语法。

    • UnchangedSaveChanges 方法对这种状态的实体什么都不会做。当从数据库读取实体之后,实体就是这种状态。

    • Modified: 实体中全部或者部分字段是这种状态时, SaveChanges 方法将发起 UPDATE 语法。

    • Deleted: 实体被标志位删除时, SaveChanges 方法将发起 DELETE 语法。

    • Detached: 实体没有被数据库上下文(database context)状态跟踪。

  2. 更改实体状态的方法

    • 使用 Context.Entry
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //1. 修改实体
      Student studentToDelete = new Student() { ID = id };
      _context.Entry(studentToDelete).State = EntityState.Deleted;
      await _context.SaveChangesAsync();

      //2. 修改字段
      //查询 id=1 的学生
      var studentToUpdate = await _context.Students.SingleOrDefaultAsync(s => s.ID == 1);//EntityState=Unchanged
      var studentEntry = _context.Entry(studentToUpdate);

      //设置为修改状态
      studentEntry.Property(s => s.LastName).IsModified = true;//EntityState=Modified
      studentEntry.Property(s => s.FirstMidName).IsModified = true;

      //保存更改
      await _context.SaveChangesAsync();

防止加载不必要的字段

  1. BindAttribute: Bind["Course","Grade"] Student s。较适用于 Creat
  2. 使用 TryUpdateModel ,传入一组委托参数,指定要更新的字段。这个方法会从Form表单读取数据,较适用于 Edit
  3. 使用 View Model ,适用于 CreatEdit 场景。做法:将view model 作为类型参数,接收数据,然后将其所有属性复制(可使用 AutoMapper )到要更改的类实例中,使用 _context.Entry 方法将实体状态更改为 Unchanged ,然后再逐个将需要应用修改的属性状态 IsModified 改为 true 。最终保存到数据库。

主要
方法1 不适用于 Edit 方法,Bind 会生成一个实例,将所有字段设置状态为 Modified ,未指定的字段会被清空。对于 Edit 方法,推荐先读取,再使用 TryUpdateModel

增删改

1.  先读实体再修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[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 });
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[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);

//TryUpdateModelAsync此方法传入一组委托,指定要更新的字段
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);
}
2.  先创建实体再修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[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 });
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
3.  使用ViewModel

    创建一个具有几个需要更改属性的 `ViewModel`

查询

  1. No-tracking 查询

    • HttpGet 方法,在上下文生命周期内(默认是 Scoped ),不需要修改任何实体,不用通过多个查询加载导航属性
    • 需要查询大量数据,仅有少量需要修改,禁用跟踪能大幅提高效率。在需要更新数据之前,先查询这些少量的数据,再更新。
    • 前面已经查询出对应数据,上下文已经跟踪该数据,但想使用 attach 方式更新该数据。如果未禁用跟踪,无法以这种方式更新
  2. 跟踪查询(默认方式)

    在上下文生命周期内(默认是 Scoped ),数据会缓存,并且一直与数据库同步。

其他

  1. 数据库连接什么时候释放
    数据库上下文是由 DI 注入,AddDbContext 默认生命周期为 Scope ,生命周期之后,最终会释放数据库连接等资源。Scope 意味着一次请求结束,生命周期结束。为什么?这里的 Scope 很有可能是 HttpContext.RequestServices 这个 ServiceProvider 创建的,或者其 子ServiceProvider 创建的,而 HttpContext 每次请求结束会释放,so…
  2. 多个修改之后 SaveChanges,EFCore 隐式实现事务。