当Attach方法存在时,我们哪里被迫使用Update方法?
例如
public void UpdateStudent(Student student)
{
var student = _context.Students.FirstOrDefault(s => s.Id == student.Id);
student.FullName = student.FullName;
student.Age = student.Age;
context.Students.Update(student); //or context.Students.Attach(student) ?
context.SaveChanges();
}
Attach 开始跟踪给定实体以及可从给定实体访问的条目,但即使您的对象与数据库中的对象不同,其状态也会开始为
Unchanged
。
更新将执行相同的操作,但以
Modified
状态开始。
var entity = context.Students.Attach(student);
entity.State = EntityState.Modified;
与
相同context.Students.Update(student);
在您的示例中,如果您在进行更改后附加,则
SaveChanges
调用不会导致任何更改保存到数据库中,因为附加后未进行任何更改。您必须附加,然后进行更改。
首先,在您的示例中,默认情况下您不需要也不应该使用
Update
。您的示例有一个拼写错误,因为您要么重新分配学生变量,要么声明重复的命名实例,这将是编译器错误,但是解决这个问题,这就是所需要的:
public void UpdateStudent(Student student)
{
var existingStudent = _context.Students.First(s => s.Id == student.Id);
existingStudent.FullName = student.FullName;
existingStudent.Age = student.Age;
context.SaveChanges();
}
这是一种“更安全”的数据更新方式。当您从
DbContext
读取实体时,默认情况下它将跟踪更改。我们读取当前行(避免使用“OrDefault”,除非您准备好处理不会返回行的可能性),我们仅复制允许更改的值,然后调用 SaveChanges
。该跟踪副本将写入任何更改。
使用
Update
的示例:
public void UpdateStudent(Student student)
{
context.Update(student);
context.SaveChanges();
}
从表面上看,这看起来更简单,并且在大多数情况下它会按预期工作,但它与第一种方法有一些显着的差异。第一个问题是,
Update
将为实体中的每一列生成一个 SQL UPDATE
语句,无论它们是否已更改。使用第一种方法,只会为实际更改的值生成 UPDATE
语句,如果值没有更改,则不会运行 UPDATE
语句。因此,如果姓名和年龄与刚刚读取的内容相同,则不会调用更新值的数据库调用。使用 Update()
会产生一些潜在的问题。首先,传入的副本可能已过时或无效,并且更改了您不打算更改的值。调用 Update()
将覆盖该行中的所有内容。
Attach()
是处理未跟踪实体的一种特殊情况。通常你会看到这样的例子:
public void UpdateStudent(Student student)
{
context.Attach(student);
context.Entry(student).State = EntityState.Modified;
context.SaveChanges();
}
这实际上与调用
Update(student)
相同,并且具有相同的漏洞。您可以使用 Attach
修改示例以确保仅更新姓名和年龄:
public void UpdateStudent(Student student)
{
context.Attach(student);
context.Entry(student).Property("Name").IsModified = true;
context.Entry(student).Property("Age").IsModified = true;
context.SaveChanges();
}
这确保只更新姓名和年龄,但无论值是否实际更改,它都会创建一个
UPDATE
语句。
像
Attach
这样的东西更常用的是简化您想要链接或删除数据而无需先从数据库读取数据的操作。例如,如果我们有一个名为“DeleteStudent”的方法,我们可以传递一个 StudentId 而不是整个独立的学生记录。
public void DeleteStudent(int studentId)
{
var student = new Student(Id = studentId);
context.Attach(student);
context.Students.Remove(student);
context.SaveChanges();
}
这里的“学生”实例不是从数据库读取的,也不代表完整的学生,但通过附加它,它可以作为数据库中一行的表示。您可以使用类似的方法创建替代引用来附加/关联行,但请记住,附加实例仅与您填充的一样完整,并不一定代表数据库中实际的完整或当前数据状态。 这是一个非常基本且不安全的示例。通常,我们首先确保调用此方法的用户实际上有权删除学生和/或特定学生。我们还应该始终首先检查不是专门为现有跟踪引用的操作创建的 DbContext 实例:
var student = context.Students.Local.FirstOrDefault(x => x.Id == studentId);
if (student == null)
{
student = new Student(Id = studentId);
context.Attach(student);
}
context.Students.Remove(student);
context.SaveChanges();
检查
.Local
不会访问数据库,它只是检查 DbContext
的跟踪缓存。如果您在新的未跟踪实例上调用 Attach
或 Update
并且 DbContext 恰好已经在跟踪具有相同 PK 的实例,您将收到错误。 (沿着“DbContext 已经在跟踪实例......”的思路)
总的来说,我的建议是使用第一种方法,而不是传递学生实体,而是传递仅表示执行更新所需的信息的内容,以便它不会与最新表示相混淆。
这应该是
Update
和 Attach
的使用的相当完整的概述,以及使用它们时需要考虑的一些事项。