ASP.NET Core 使用 Entity Framework 新增修改数据
本章节将讲解如何通过数据库上下午对象 DbContext 来新增或修改数据。
先看一个简单的例子,新增一个产品。右键单击项目的 Controllers,选择 添加-新建项,在弹出的对话框中选择 API 控制器 - 空。命名为 EmplController.cs 文件,首先在构造函数注入数据库上下文对象,添加 Add 方法,代码如下:
namespace MyFirstCoreWebApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly MyFirstCoreWebAppDbContext _context;
public EmployeeController(MyFirstCoreWebAppDbContext context)
{
_context = context;
}
public async Task<ActionResult> Add(Employee employee)
{
employee = new Employee
{
EmployeeName = "李雷",
EntryDate = DateTime.Parse("2023-1-1")
};
_context.Add(employee);
await _context.SaveChangesAsync();
return NoContent();
}
}
}
执行这个 Add
方法后查看数据库的 Employee 表,可以看到有了一条 "李雷" 的新纪录。现在看看 Add
函数都做了些什么事情:可以看到先是创建一个产品实例,接着调用了上下文对象的 Add
方法和 SaveChangesAsync
方法。
那么,_context.Add
和 _context.SaveChangesAsync
这两个方法有什么区别呢?毫无疑问,SaveChangesAsync
方法是最后执行的方法,通过这个方法将 "李雷" 这条数据保存至数据库。那么上下文对象 _context.Add
方法又是干什么用的?为什么要有这个方法?这就涉及到上下文对象的一个很重要的概念:跟踪。
数据保存的方法有新增、修改、删除三种,在上下文对象将数据保存至数据库前(调用 SaveChangesAsync
方法),得先准备好要保存至数据库的数据,同时要知道这些数据的状态,即是保存至数据库时要执行的是新增、修改、还是删除方法,于是就有了 跟踪 的概念,比如有以下三个员工实例:
var employeeAdd = new Employee { EmployeeName = "韩梅梅", EntryDate = DateTime.Now };
var employeeModified = new Employee { ID = 1, EmployeeName = "李雷1", EntryDate = DateTime.Now.AddDays(-1) };
var employeeDel = new Employee { ID = 2 };
分别调用上下文对象的 Add
、Updae
、Remove
方法来跟踪这三个员工实例:
_context.Add(employeeAdd);
_context.Update(employeeModified);
_context.Remove(employeeDel);
现在这三个员工对象已经被上下文跟踪,它们的状态分别是:
- Added:新增状态,需要注意的是这个员工实例是没有ID数据的。
- Modified:修改状态,有员工 ID 数据(假设数据库有这个员工)。
- Deleted:删除状态,对于本例而言有员工 ID 数据即可。
也可以通过以下方法查看上下文对象正在跟踪的数据及数据状态:
_context.Add(employeeAdd);
_context.Update(employeeModified);
_context.Remove(employeeDel);
Console.WriteLine(_context.ChangeTracker.DebugView.LongView);
运行代码后,通过 输出 窗口(如果未打开可通过 Ctrl+Alt+O 快捷键打开)查看,其中 显示输出来源 选择 [项目名称] - ASP.NET Core Web 服务器,即可查看到如下图所示的跟踪信息:
不管上下文对象跟踪了多少数据,只要被跟踪的数据处于 Added、Modified、或 Deleted 状态,都会一次性提交到数据库,而且是作为事务进行提交的,要么全部成功,要么全部不成功。
从上面的 "跟踪数据" 图可以看到,修改时是每一个字段都会修改的。但有时可能只是修改员工名称字段,其他字段不需要修改,这时还是把所有字段都发送到数据库去修改,性能就不太好了,尤其是对于字段比较多,数据量比较大时。能不能做到只把需要修改的字段发送到数据库呢?
当然可以的,先看看上下文实例的另一个方法:Attach
。上面介绍了通过上下文的 Add
、Updae
、Remove
方法可以跟踪数据,而方法 Attach
也可以把数据附加到上下文实例进行跟踪,只是数据的状态是 Unchanged,未更改状态的,下面看看如果通过这个方法实现只修改需要修改的字段:
var employeeDto = new Employee { ID = 1, EmployeeName = "韩梅梅", EntryDate = DateTime.Now };
_context.Attach(employeeDto);
var employeeModified = new Employee { ID = 1, EmployeeName = "韩梅梅", EntryDate = DateTime.Now.AddDays(-1) };
_context.Entry(employeeDto).CurrentValues.SetValues(employeeModified);
Console.WriteLine(_context.ChangeTracker.DebugView.LongView);
假设 employeeDto 是员工的原始数据,通过 _context.Attach(employeeDto);
将原始数据附加到上下文实例进行跟踪。employeeModified 是修改后的数据,通过上下文的 CurrentValues.SetValues
方法将修改过的数据设置为当前值,上下文的另一个属性:OriginalValues.SetValues
的方法就是设置被跟踪实体的原始值,调用 Attach
方法时就自动设置了它的原始值。也就是说,被跟踪的实体实例 employeeDto 它的原始值是 employeeDto,当前值是 employeeModified** ,比较它们的值可以发现只有字段 EntryDate 的值是不一样的,其他字段的值都是一样的,现在执行保存数据的方法看看有什么不一样:
这时上下文对象只是把 EntryDate 字段发送到数据。上面的例子原始的数据是通过数据传输对象(DTO)设置的,更常用的方法是查询数据库:
var employeeDto = _context.Employees.Find(1);
var employeeModified = new Employee { ID = 1, EmployeeName = "韩梅梅", EntryDate = DateTime.Now.AddDays(-1) };
_context.Entry(employeeDto).CurrentValues.SetValues(employeeModified);
Console.WriteLine(_context.ChangeTracker.DebugView.LongView);
先通过查询数据库把原始值赋给 employeeDto,这时就不用调用 Attach
这个方法了,因为查询数据后上下文对象会自动跟踪查询到的数据(查询数据的详细方法将在下篇介绍)。效果和前面通过 Attach
方法是一样的。最后也只是发送 EntryDate 字段到数据库进行修改。
如果有 SQL Server Profiler,我们也可以监控一下生成的 SQL 语句:
从上图中反应,EF 先是调用了存储过程获取了 ID 为 1 的记录,然后只更新了这条记录的 EntryDate 字段。