EF Core 修改后的实体状态如何表现?

How does EF Core Modified Entity State behave?

我们将实体 state = modified 放在更改之后还是进行更改之前重要吗?

using (var db = new LakshyaContext())
{
    foreach (var category in db.Categories)
    {
        db.Entry(category).State = EntityState.Modified; // before
        category.Count = 25; //Making Changes
        db.Entry(category).State = EntityState.Modified; //After
    }

    db.SaveChanges();
}

所以首先,让我们把最重要的事情放在一边:

你是对的。在您的示例中,您不需要手动调用 db.Entry(category).State = EntityState.Modified。这是因为您正在从上面的上下文中加载条目(类别)。这被称为“连接场景”,其中 DbContext 知道实体, 跟踪它们。这是相同的,例如在 ASP.NET 核心应用程序中,上下文在 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 class 上的普通汽车 属性? DetectChanges 方法背后的魔法。

在某些特定情况下,DbContext 调用 DetectChanges 方法。最明显的一个是调用 SaveChanges 时。在顶层,它的工作方式是:

  1. DbContext 为其加载的每个实体制作快照
  2. SaveChanges 被调用时,它会继续调用 DetectChanges,这将很神奇地找出发生了什么变化。
  3. DbContext 然后负责将正确的命令发送到数据库。

至此,我们知道DetectChanges的责任了。现在重要的部分是知道何时调用 DetectChanges(除了我们已经知道的 SaveChanges)。这对于最终回答您的“订单”问题至关重要。来自 Arthur Vickers

的链接文章

The methods that call 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
...

现在说几点:

以上在断开连接的情况下的更新将在 table 中的 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