首先,让我们把最重要的事情弄清楚:
你是对的。在您的示例中,您不需要手动调用db.Entry(category).State = EntityState.Modified。这是因为您正在从上面的上下文中加载条目(类别)。这被称为“连接场景”,DbContext 知道实体,它正在跟踪它们。这也是一样的,例如在 ASP.NET Core 应用程序中,上下文在 HTTP 请求之间共享。
您在using (var db = new LakshyaContext()) 范围内所做的任何修改,都会在您调用SaveChanges 时被上下文所知。
现在,在处理断开连接的场景(如您所说的 UnTracked 实体)时,我们必须更深入地挖掘。
要理解这一点,首先您需要知道DbContext 如何知道发生了什么变化。举个例子:
using (var context = new MyContext())
{
// loads the book by it's ISBN
var book = context.Books
.Single(p => p.ISBN == "123456");
// Do changes
book.Price = 30;
// Save changes
context.SaveChanges();
}
它怎么知道Price 改变了?因为它只是Book 类的普通自动属性? DetectChanges 方法背后的魔力。
在某些特定情况下,DbContext 调用DetectChanges 方法。最明显的是当SaveChanges 被调用时。在顶层,它的工作方式是:
-
DbContext 对其加载的每个实体进行快照
- 当
SaveChanges 被调用时,它会继续调用DetectChanges,这会很神奇地弄清楚发生了什么变化。
-
DbContext 然后负责向数据库发送正确的命令。
至此,我们知道DetectChanges的责任。现在重要的部分是知道何时调用DetectChanges(除了我们已经知道的SaveChanges)。这对于最终回答您的“订单”问题至关重要。来自Arthur Vickers的链接文章
调用 DetectChanges 的方法:
- DbSet.Find
- DbSet.Local
- DbSet.Remove
- DbSet.Add
- DbSet.Attach
- DbContext.SaveChanges
- DbContext.GetValidationErrors
- DbContext.Entry
- DbChangeTracker.Entries
让我们检查一下演示“断开连接”场景的代码。
public Task UpdateBook()
{
Book book = null;
// Just loads the book from this context
using (var context = new MyContext())
{
book = context.Books
.Single(p => p.ISBN == "123456");
}
// Starts a new context where the book is going to be updated
using (var anotherContext = new MyContext())
{
// Changed the price - remember this is not loaded from this context!
book.Price = 40;
// THIS IS KEY: This will call `DetectChanges`
// This entity will be tracked by the context now
anotherContext.Entry(book).State = EntityState.Modified
// Update will occur normally
anotherContext.SaveChanges();
}
}
当我们进入第二个DbContext, 时,它并不知道我们的book 实体。我们更改价格,然后致电db.Entry(book).State = EntityState.Modified。此时,DbContext 将开始跟踪它,并调用DetectChanges。继续调用SaveChanges 将按预期工作。
如果我们反其道而行之,在实际更改价格之前致电db.Entry(book).State = EntityState.Modified 将......仍然有效!
为什么?好吧,使用db.Entry(book).State 手动更改实体的状态会将实体添加到上下文中,这意味着它将开始跟踪它的更改。
因此,即使我们调用 db.Entry(book).State 然后在实体上应用更改也没关系,因为最后调用 SaveChanges 将触发 再次 DetectChanges,因为之前已经调用过,该实体已经有一个快照。
您可以自己验证此行为的一种方法是运行上面的代码并为DbContext启用日志记录:
// Calling db.Entry.. produces this log:
DetectChanges starting for 'MyContext'.
Microsoft.EntityFrameworkCore.ChangeTracking:Debug: DetectChanges completed for 'MyContext'.
Context 'MyContext' started tracking 'Book' entity.
// Calling SaveChanges produces this log:
SaveChanges starting for 'MyContext'
DetectChanges starting for 'MyContext'.
DetectChanges completed for 'MyContext'.
Opening connection to database 'BooksDB'
Beginning transaction with isolation
...
现在说几句:
上述断开连接场景中的更新将在表中的ALL COLUMNS上发布更新。这可能不是您所期望的。有办法防止这种情况发生。 Read more here
DetectChanges 在内部做了很多事情,不仅仅是对更改应用合并。它负责外键、更新导航属性的引用等,并进行“修复”。
更多资源可供阅读:(尤其是来自 Arthur Vickers 的资源!)
Secrets of DetectChanges Part 1: What does DetectChanges do?
Secrets of DetectChanges Part 2: When is DetectChanges called automatically?
Possible Issue with Change Tracker Caching Entity State EF Core 2.0.2
Working with Disconnected Entity Graph in Entity Framework Core
Entity Framework Core TrackGraph For Disconnected Data