ASP.NET Core 使用 Entity Framework 新增修改数据

本章节将讲解如何通过数据库上下午对象 DbContext 来新增或修改数据。

先看一个简单的例子,新增一个产品。右键单击项目的 Controllers,选择 添加-新建项,在弹出的对话框中选择 API 控制器 - 空。命名为 EmplController.cs 文件,首先在构造函数注入数据库上下文对象,添加 Add 方法,代码如下:

  1. namespace MyFirstCoreWebApp.Controllers
  2. {
  3. [Route("api/[controller]")]
  4. [ApiController]
  5. public class EmployeeController : ControllerBase
  6. {
  7. private readonly MyFirstCoreWebAppDbContext _context;
  8. public EmployeeController(MyFirstCoreWebAppDbContext context)
  9. {
  10. _context = context;
  11. }
  12. public async Task<ActionResult> Add(Employee employee)
  13. {
  14. employee = new Employee
  15. {
  16. EmployeeName = "李雷",
  17. EntryDate = DateTime.Parse("2023-1-1")
  18. };
  19. _context.Add(employee);
  20. await _context.SaveChangesAsync();
  21. return NoContent();
  22. }
  23. }
  24. }

执行这个 Add 方法后查看数据库的 Employee 表,可以看到有了一条 "李雷" 的新纪录。现在看看 Add 函数都做了些什么事情:可以看到先是创建一个产品实例,接着调用了上下文对象的 Add 方法和 SaveChangesAsync 方法。

那么,_context.Add_context.SaveChangesAsync 这两个方法有什么区别呢?毫无疑问,SaveChangesAsync 方法是最后执行的方法,通过这个方法将 "李雷" 这条数据保存至数据库。那么上下文对象 _context.Add 方法又是干什么用的?为什么要有这个方法?这就涉及到上下文对象的一个很重要的概念:跟踪

数据保存的方法有新增、修改、删除三种,在上下文对象将数据保存至数据库前(调用 SaveChangesAsync 方法),得先准备好要保存至数据库的数据,同时要知道这些数据的状态,即是保存至数据库时要执行的是新增、修改、还是删除方法,于是就有了 跟踪 的概念,比如有以下三个员工实例:

  1. var employeeAdd = new Employee { EmployeeName = "韩梅梅", EntryDate = DateTime.Now };
  2. var employeeModified = new Employee { ID = 1, EmployeeName = "李雷1", EntryDate = DateTime.Now.AddDays(-1) };
  3. var employeeDel = new Employee { ID = 2 };

分别调用上下文对象的 AddUpdaeRemove 方法来跟踪这三个员工实例:

  1. _context.Add(employeeAdd);
  2. _context.Update(employeeModified);
  3. _context.Remove(employeeDel);

现在这三个员工对象已经被上下文跟踪,它们的状态分别是:

  • Added:新增状态,需要注意的是这个员工实例是没有ID数据的。
  • Modified:修改状态,有员工 ID 数据(假设数据库有这个员工)。
  • Deleted:删除状态,对于本例而言有员工 ID 数据即可。

也可以通过以下方法查看上下文对象正在跟踪的数据及数据状态:

  1. _context.Add(employeeAdd);
  2. _context.Update(employeeModified);
  3. _context.Remove(employeeDel);
  4. Console.WriteLine(_context.ChangeTracker.DebugView.LongView);

运行代码后,通过 输出 窗口(如果未打开可通过 Ctrl+Alt+O 快捷键打开)查看,其中 显示输出来源 选择 [项目名称] - ASP.NET Core Web 服务器,即可查看到如下图所示的跟踪信息:

不管上下文对象跟踪了多少数据,只要被跟踪的数据处于 AddedModified、或 Deleted 状态,都会一次性提交到数据库,而且是作为事务进行提交的,要么全部成功,要么全部不成功。

从上面的 "跟踪数据" 图可以看到,修改时是每一个字段都会修改的。但有时可能只是修改员工名称字段,其他字段不需要修改,这时还是把所有字段都发送到数据库去修改,性能就不太好了,尤其是对于字段比较多,数据量比较大时。能不能做到只把需要修改的字段发送到数据库呢?

当然可以的,先看看上下文实例的另一个方法:Attach。上面介绍了通过上下文的 AddUpdaeRemove 方法可以跟踪数据,而方法 Attach 也可以把数据附加到上下文实例进行跟踪,只是数据的状态是 Unchanged,未更改状态的,下面看看如果通过这个方法实现只修改需要修改的字段:

  1. var employeeDto = new Employee { ID = 1, EmployeeName = "韩梅梅", EntryDate = DateTime.Now };
  2. _context.Attach(employeeDto);
  3. var employeeModified = new Employee { ID = 1, EmployeeName = "韩梅梅", EntryDate = DateTime.Now.AddDays(-1) };
  4. _context.Entry(employeeDto).CurrentValues.SetValues(employeeModified);
  5. Console.WriteLine(_context.ChangeTracker.DebugView.LongView);

假设 employeeDto 是员工的原始数据,通过 _context.Attach(employeeDto); 将原始数据附加到上下文实例进行跟踪。employeeModified 是修改后的数据,通过上下文的 CurrentValues.SetValues 方法将修改过的数据设置为当前值,上下文的另一个属性:OriginalValues.SetValues 的方法就是设置被跟踪实体的原始值,调用 Attach 方法时就自动设置了它的原始值。也就是说,被跟踪的实体实例 employeeDto 它的原始值是 employeeDto,当前值是 employeeModified** ,比较它们的值可以发现只有字段 EntryDate 的值是不一样的,其他字段的值都是一样的,现在执行保存数据的方法看看有什么不一样:

这时上下文对象只是把 EntryDate 字段发送到数据。上面的例子原始的数据是通过数据传输对象(DTO)设置的,更常用的方法是查询数据库:

  1. var employeeDto = _context.Employees.Find(1);
  2. var employeeModified = new Employee { ID = 1, EmployeeName = "韩梅梅", EntryDate = DateTime.Now.AddDays(-1) };
  3. _context.Entry(employeeDto).CurrentValues.SetValues(employeeModified);
  4. Console.WriteLine(_context.ChangeTracker.DebugView.LongView);

先通过查询数据库把原始值赋给 employeeDto,这时就不用调用 Attach 这个方法了,因为查询数据后上下文对象会自动跟踪查询到的数据(查询数据的详细方法将在下篇介绍)。效果和前面通过 Attach 方法是一样的。最后也只是发送 EntryDate 字段到数据库进行修改。

如果有 SQL Server Profiler,我们也可以监控一下生成的 SQL 语句:

从上图中反应,EF 先是调用了存储过程获取了 ID 为 1 的记录,然后只更新了这条记录的 EntryDate 字段。

分类导航