中斷連接的實體Disconnected entities

DbContext 執行個體將會自動追蹤從資料庫傳回的實體。A DbContext instance will automatically track entities returned from the database. 當呼叫 SaveChanges,並視需要將更新的資料庫,然後將偵測這些實體所做的變更。Changes made to these entities will then be detected when SaveChanges is called and the database will be updated as needed. 請參閱基本儲存相關資料如需詳細資訊。See Basic Save and Related Data for details.

不過,有時候實體會讓查詢使用一個內容執行個體,然後使用不同的執行個體來儲存。However, sometimes entities are queried using one context instance and then saved using a different instance. 這通常發生在 已中斷連線 」 的情況下,例如 web 應用程式,其中實體的查詢、 傳送至用戶端、 修改、 傳送至伺服器,在要求中,而且再儲存。This often happens in "disconnected" scenarios such as a web application where the entities are queried, sent to the client, modified, sent back to the server in a request, and then saved. 在此情況下,第二個內容執行個體需要知道實體是否為新 (應插入),或現有的 (應該更新)。In this case, the second context instance needs to know whether the entities are new (should be inserted) or existing (should be updated).

提示

您可以檢視這篇文章範例GitHub 上。You can view this article's sample on GitHub.

識別新的實體Identifying new entities

用戶端識別出新的實體Client identifies new entities

簡單的情況下,處理時,用戶端通知伺服器實體是新的或現有。The simplest case to deal with is when the client informs the server whether the entity is new or existing. 例如,通常要插入新實體的要求與不同更新現有實體的要求。For example, often the request to insert a new entity is different from the request to update an existing entity.

本章節的其餘部分涵蓋的情況下,必須決定是以其他某種方式來插入或更新。The remainder of this section covers the cases where it necessary to determine in some other way whether to insert or update.

使用自動產生的索引鍵With auto-generated keys

自動產生的索引鍵的值通常可用來判斷實體是否需要插入或更新。The value of an automatically generated key can often be used to determine whether an entity needs to be inserted or updated. 如果機碼不具有已設定 (也就是它仍然具有 null、 零 」 等的 CLR 預設值),則必須是新實體,而且需要插入。If the key has not been set (i.e. it still has the CLR default value of null, zero, etc.), then the entity must be new and needs inserting. 相反地,如果已設定的索引鍵值,然後它必須已先前儲存,現在需要更新。On the other hand, if the key value has been set, then it must have already been previously saved and now needs updating. 換句話說,如果索引鍵的值,然後實體查詢,傳送至用戶端,而現在是回更新。In other words, if the key has a value, then entity was queried, sent to the client, and has now come back to be updated.

很容易知道實體類型時,檢查為未設定的索引鍵:It is easy to check for an unset key when the entity type is known:

public static bool IsItNew(Blog blog) 
    => blog.BlogId == 0;

不過,EF 也有內建的方法,若要這樣做的任何實體類型和索引鍵類型:However, EF also has a built-in way to do this for any entity type and key type:

public static bool IsItNew(DbContext context, object entity) 
    => !context.Entry(entity).IsKeySet;

提示

索引鍵會設定為實體所追蹤的內容,即使實體處於 Added 狀態。Keys are set as soon as entities are tracked by the context, even if the entity is in the Added state. 這有助於周遊的實體,並決定如何處理每個,如為使用 TrackGraph API 時圖形時。This helps when traversing a graph of entities and deciding what to do with each, such as when using the TrackGraph API. 索引鍵的值應該只用於如下所示的方式_之前_進行任何呼叫,以追蹤實體。The key value should only be used in the way shown here before any call is made to track the entity.

其他索引鍵With other keys

其他一些機制才能識別新的實體時不會自動產生索引鍵值。Some other mechanism is needed to identity new entities when key values are not generated automatically. 有兩種一般的方法,這個:There are two general approaches to this:

  • 查詢實體Query for the entity
  • 從用戶端傳遞旗標Pass a flag from the client

若要查詢的實體,只要使用 Find 方法:To query for the entity, just use the Find method:

public static bool IsItNew(BloggingContext context, Blog blog)
    => context.Blogs.Find(blog.BlogId) == null;

已超出本文件顯示旗標傳遞從用戶端的完整程式碼的範圍。It is beyond the scope of this document to show the full code for passing a flag from a client. 在 web 應用程式中,通常表示不同針對提出要求不同的動作,或傳遞要求中的某種狀態,然後擷取控制器中。In a web app, it usually means making different requests for different actions, or passing some state in the request then extracting it in the controller.

儲存單一實體Saving single entities

如果已知是否需要插入或更新,則可以適當地使用新增或更新:If it is known whether or not an insert or update is needed, then either Add or Update can be used appropriately:

public static void Insert(DbContext context, object entity)
{
    context.Add(entity);
    context.SaveChanges();
}

public static void Update(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

不過,如果實體使用自動產生索引鍵值,然後更新方法可以用於這兩種情況:However, if the entity uses auto-generated key values, then the Update method can be used for both cases:

public static void InsertOrUpdate(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

更新方法通常會將針對更新、 不插入實體標記。The Update method normally marks the entity for update, not insert. 不過,如果實體具有自動產生的金鑰,而且沒有索引鍵的值已設定,則實體改為自動標示為進行插入。However, if the entity has a auto-generated key, and no key value has been set, then the entity is instead automatically marked for insert.

提示

此行為是在 EF 核心 2.0 引進。This behavior was introduced in EF Core 2.0. 較早的版本則一律需要明確地選擇 新增或更新。For earlier releases it is always necessary to explicitly choose either Add or Update.

如果實體不使用自動產生的索引鍵,則應用程式必須決定是否應該插入或更新實體: 例如:If the entity is not using auto-generated keys, then the application must decide whether the entity should be inserted or updated: For example:

public static void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }

    context.SaveChanges();
}

這裡的步驟如下:The steps here are:

  • 如果尋找傳回 null,則資料庫已不包含具有此 ID,部落格,讓我們呼叫將標示插入作業。If Find returns null, then the database doesn't already contain the blog with this ID, so we call Add mark it for insertion.
  • 如果尋找傳回實體,然後它存在於資料庫中,且內容現在追蹤現有的實體If Find returns an entity, then it exists in the database and the context is now tracking the existing entity
    • 然後使用 SetValues 來自用戶端的這個實體上設定的所有屬性的值。We then use SetValues to set the values for all properties on this entity to those that came from the client.
    • SetValues 呼叫會將標示為要視需要更新實體。The SetValues call will mark the entity to be updated as needed.

提示

SetValues 只會將已修改具有不同的值中的追蹤實體的屬性。SetValues will only mark as modified the properties that have different values to those in the tracked entity. 這表示當更新傳送時,將會更新已實際變更這些資料行。This means that when the update is sent, only those columns that have actually changed will be updated. (和如果沒有任何變更,則不會更新將會寄完全)。(And if nothing has changed, then no update will be sent at all.)

使用圖形Working with graphs

所有新的或全部現有的實體All new/all existing entities

使用圖形的範例是插入或更新的部落格與它相關聯的文章的集合。An example of working with graphs is inserting or updating a blog together with its collection of associated posts. 如果應該插入圖形中的所有實體,或應該更新所有,則處理程序會使用相同的單一實體的上述。If all the entities in the graph should be inserted, or all should be updated, then the process is the same as described above for single entities. 例如,部落格和文章像這樣建立的圖表:For example, a graph of blogs and posts created like this:

var blog = new Blog
{
    Url = "http://sample.com",
    Posts = new List<Post>
    {
        new Post {Title = "Post 1"},
        new Post {Title = "Post 2"},
    }
};

可插入像這樣:can be inserted like this:

public static void InsertGraph(DbContext context, object rootEntity)
{
    context.Add(rootEntity);
    context.SaveChanges();
}

部落格和要插入的所有文章時,會將標記加入呼叫。The call to Add will mark the blog and all the posts to be inserted.

同樣地,如果在圖表中的所有實體都必須更新,然後更新可用:Likewise, if all the entities in a graph need to be updated, then Update can be used:

public static void UpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

部落格和其所有的文章將會標示要更新。The blog and all its posts will be marked to be updated.

混用新的和現有的實體Mix of new and existing entities

具有自動產生索引鍵更新一次可用進行插入和更新,即使圖形包含混用需要插入的實體和需要更新:With auto-generated keys, Update can again be used for both inserts and updates, even if the graph contains a mix of entities that require inserting and those that require updating:

public static void InsertOrUpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

更新會將標示為圖形、 部落格或 post,如果它沒有機碼值組,而其他所有實體會都標示為更新的插入作業中的任何實體。Update will mark any entity in the graph, blog or post, for insertion if it does not have a key value set, while all other entities are marked for update.

之前,不使用自動產生索引鍵時的查詢和某些處理,可以使用:As before, when not using auto-generated keys, a query and some processing can be used:

public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }
    }

    context.SaveChanges();
}

處理刪除Handling deletes

刪除可能很困難,因為處理通常不存在的實體表示,應該刪除。Delete can be tricky to handle since often the absence of an entity means that it should be deleted. 為了解決這個問題的一個方式是使用 「 虛刪除 」 的實體標記為刪除,而不實際刪除。One way to deal with this is to use "soft deletes" such that the entity is marked as deleted rather than actually being deleted. 刪除,然後更新一樣。Deletes then becomes the same as updates. 非永久性刪除可以實作在使用查詢篩選器Soft deletes can be implemented in using query filters.

為 true 的刪除,常見的模式是用於執行為何基本上 graph 差異查詢模式的延伸模組例如:For true deletes, a common pattern is to use an extension of the query pattern to perform what is essentially a graph diff. For example:

public static void InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }

        foreach (var post in existingBlog.Posts)
        {
            if (!blog.Posts.Any(p => p.PostId == post.PostId))
            {
                context.Remove(post);
            }
        }
    }

    context.SaveChanges();
}

TrackGraphTrackGraph

就內部而言,新增、 Attach 和更新使用圖形周遊決定,對未變更對於是否它應該標記為加入 (若要插入),(若要更新) 的修改日期,每個實體 (不執行任何動作),或刪除 (若要刪除)。Internally, Add, Attach, and Update use graph-traversal with a determination made for each entity as to whether it should be marked as Added (to insert), Modified (to update), Unchanged (do nothing), or Deleted (to delete). 這項機制會經由 TrackGraph API 公開。This mechanism is exposed via the TrackGraph API. 例如,假設,當用戶端傳送回之實體的圖表,它會設定某些旗標指出它應該要如何處理每個實體。For example, let's assume that when the client sends back a graph of entities it sets some flag on each entity indicating how it should be handled. TrackGraph 可用來處理這個旗標:TrackGraph can then be used to process this flag:

public static void SaveAnnotatedGraph(DbContext context, object rootEntity)
{
    context.ChangeTracker.TrackGraph(
        rootEntity,
        n =>
        {
            var entity = (EntityBase)n.Entry.Entity;
            n.Entry.State = entity.IsNew
                ? EntityState.Added
                : entity.IsChanged
                    ? EntityState.Modified
                    : entity.IsDeleted
                        ? EntityState.Deleted
                        : EntityState.Unchanged;
        });

    context.SaveChanges();
}

旗標只會顯示為實體為了簡單起見,此範例的一部分。The flags are only shown as part of the entity for simplicity of the example. 通常旗標會是 DTO 或要求中包含的其他特定狀態的一部分。Typically the flags would be part of a DTO or some other state included in the request.