與實體狀態合作

本主題將涵蓋如何在上下文中新增與附加實體,以及 Entity Framework 如何在 SaveChange 過程中處理這些實體。 Entity Framework 負責追蹤實體在連接資料庫上下文時的狀態,但在未連接或 N 層應用的情況下,你可以告訴 Entity Framework (EF) 你的實體應設定成什麼狀態。 本主題中展示的技術同樣適用於使用 Code First 與 EF Designer 所建立的模型。

實體狀態與 SaveChanges

一個實體可以處於 EntityState 列舉定義的五種狀態之一。 這些州包括:

  • 補充:該實體正在被上下文追蹤,但尚未出現在資料庫中
  • 不變:該實體被上下文追蹤並存在於資料庫中,且其屬性值與資料庫中的值相同
  • 修改:該實體被上下文追蹤並存在於資料庫中,且其部分或全部屬性值已被修改
  • 已刪除:該實體被上下文追蹤並存在於資料庫中,但下次呼叫 SaveChanges 時已被標記為刪除
  • 分離:實體未被背景追蹤

SaveChanges 對不同狀態的實體有不同的處理:

  • 未更改的實體不會被 SaveChanges 影響。 未變更狀態的實體不會更新到資料庫。
  • 新增的實體會入資料庫,當 SaveChanges 返回時會變成未變更。
  • 修改過的實體會在資料庫中更新,當 SaveChanges 回傳時會變成未更動。
  • 刪除的實體會從資料庫中刪除,然後從上下文中分離。

以下範例展示了如何改變實體或實體圖的狀態。

新增實體到上下文中

可以透過呼叫 DbSet 上的 Add 方法,將新實體加入上下文中。 這會讓該實體進入 Added 狀態,也就是說下次呼叫 SaveChanges 時,該實體會入資料庫。 例如:

using (var context = new BloggingContext())
{
    var blog = new Blog { Name = "ADO.NET Blog" };
    context.Blogs.Add(blog);
    context.SaveChanges();
}

另一種新增實體到上下文的方法是將其狀態更改為「新增」。 例如:

using (var context = new BloggingContext())
{
    var blog = new Blog { Name = "ADO.NET Blog" };
    context.Entry(blog).State = EntityState.Added;
    context.SaveChanges();
}

最後,你還可以把一個新實體加入上下文,連接到已經被追蹤的另一個實體上。 這可以透過將新實體加入另一個實體的集合導覽屬性,或設定另一個實體的參考導覽屬性指向新實體。 例如:

using (var context = new BloggingContext())
{
    // Add a new User by setting a reference from a tracked Blog
    var blog = context.Blogs.Find(1);
    blog.Owner = new User { UserName = "johndoe1987" };

    // Add a new Post by adding to the collection of a tracked Blog
    blog.Posts.Add(new Post { Name = "How to Add Entities" });

    context.SaveChanges();
}

請注意,在所有這些例子中,如果被新增的實體有尚未追蹤的其他實體的參考,這些新實體也會被加入上下文中,並在下次呼叫 SaveChanges 時插入資料庫。

將現有實體附加於上下文

如果你有一個實體你知道資料庫中已經存在,但目前上下文並未追蹤它,你可以用 DbSet 上的 Attach 方法告訴上下文去追蹤該實體。 該實體在上下文中將處於未變更狀態。 例如:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Blogs.Attach(existingBlog);

    // Do some more work...  

    context.SaveChanges();
}

請注意,若在未對附加實體進行其他操作的情況下呼叫 SaveChanges,資料庫不會有任何變更。 這是因為該實體處於未改變的狀態。

另一種將現有實體附加到上下文的方法是將其狀態更改為未改變。 例如:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Unchanged;

    // Do some more work...  

    context.SaveChanges();
}

請注意,在這兩個例子中,如果所附的實體具有尚未追蹤的其他實體的引用,那麼這些新實體也會以未變更狀態附加到上下文中。

將已存在但經過修改的實體附加到上下文中

如果你知道資料庫中已有實體,但可能已經對該實體做了修改,你可以告訴上下文附加該實體並設定為修改狀態。 例如:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Modified;

    // Do some more work...  

    context.SaveChanges();
}

當你將狀態改為 Modified 時,該實體的所有屬性都會被標記為已修改,且所有屬性值會在呼叫 SaveChanges 時傳送到資料庫。

請注意,若被附加的實體有尚未追蹤的其他實體的參考,這些新實體將以未變更狀態附加到上下文中,不會自動被修改。 如果你有多個需要標記為「修改」的實體,應該分別為每個實體設定狀態。

變更追蹤實體的狀態

你可以透過設定實體條目中的 State 屬性,來更改已被追蹤的實體狀態。 例如:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Blogs.Attach(existingBlog);
    context.Entry(existingBlog).State = EntityState.Unchanged;

    // Do some more work...  

    context.SaveChanges();
}

請注意,呼叫 Add 或 Attach 方法於已正被追蹤的實體,也可以用來更改實體狀態。 例如,呼叫目前處於新增狀態的實體 Attach,會將其狀態改為未變更。

插入或更新範本

某些應用程式常見的模式是根據主鍵值,將實體新增為新實體(產生資料庫插入)或附加實體為現有實體並標記為修改(導致資料庫更新)。 例如,使用資料庫產生的整數主鍵時,通常會將鍵數為零的實體視為新金鑰,而非零鍵的實體視為已存在。 此模式可透過根據主鍵值的檢查設定實體狀態來達成。 例如:

public void InsertOrUpdate(Blog blog)
{
    using (var context = new BloggingContext())
    {
        context.Entry(blog).State = blog.BlogId == 0 ?
                                   EntityState.Added :
                                   EntityState.Modified;

        context.SaveChanges();
    }
}

請注意,當你將狀態改為 Modified 時,該實體的所有屬性都會被標記為已修改,且當呼叫 SaveChanges 時,所有屬性值都會被傳送到資料庫。