エンティティの状態の操作

このトピックでは、エンティティをコンテキストに追加およびアタッチする方法と、Entity Framework が SaveChanges の実行時にこれらを処理する方法について説明します。 エンティティがコンテキストに接続されている間は、Entity Framework によって状態が追跡されますが、切断されているシナリオや N 層のシナリオでは、エンティティのあるべき状態を EF に通知できます。 このトピックで紹介するテクニックは、Code First および EF Designer で作成されたモデルに等しく使用できます。

エンティティの状態と SaveChanges

エンティティは、EntityState 列挙型で定義されている 5 つの状態のいずれかになります。 状態には次のものがあります。

  • Added: エンティティはコンテキストによって追跡されていますが、データベースにはまだ存在していません。
  • Unchanged: エンティティはコンテキストによって追跡されており、データベースに存在します。そのプロパティ値は、データベース内の値から変更されていません。
  • Modified: エンティティはコンテキストによって追跡されており、データベースに存在します。そのプロパティ値の一部またはすべてが変更されています。
  • Deleted: エンティティはコンテキストによって追跡されており、データベースに存在しますが、次回 SaveChanges が呼び出されたときに、データベースから削除するようにマークされています。
  • Detached: エンティティはコンテキストによって追跡されていません。

SaveChanges によって実行される処理は、エンティティの状態によってそれぞれ異なります。

  • Unchanged のエンティティは、SaveChanges の影響を受けません。 Unchanged 状態のエンティティの更新はデータベースに送信されません。
  • Added のエンティティはデータベースに挿入され、SaveChanges から制御が戻ると、Unchanged になります。
  • Modified のエンティティはデータベースで更新され、SaveChanges から制御が戻ると、Unchanged になります。
  • Deleted のエンティティはデータベースから削除され、コンテキストからデタッチされます。

以下の例は、エンティティまたはエンティティ グラフの状態を変更する方法を示しています。

新しいエンティティをコンテキストに追加する

新しいエンティティをコンテキストに追加するには、DbSet の Add メソッドを呼び出します。 これにより、エンティティは Added 状態になります。つまり、次回 SaveChanges が呼び出されたときにデータベースに挿入されます。 次に例を示します。

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

新しいエンティティをコンテキストに追加する別の方法として、状態を Added に変更します。 次に例を示します。

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 メソッドを使用して、そのエンティティを追跡するようコンテキストに指示できます。 エンティティは、コンテキストで Unchanged 状態になります。 次に例を示します。

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 が呼び出された場合、データベースは変更されません。 これは、エンティティが Unchanged 状態であるためです。

既存のエンティティをコンテキストにアタッチするもう 1 つの方法は、状態を Unchanged に変更することです。 次に例を示します。

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();
}

どちらの例でも、アタッチされるエンティティに、まだ追跡されていない他のエンティティへの参照が含まれている場合、これらの新しいエンティティも、Unchanged 状態でコンテキストにアタッチされます。

既存の変更済みエンティティをコンテキストにアタッチする

データベースに既に存在することがわかっており、変更されている可能性のあるエンティティがある場合は、そのエンティティをアタッチし、状態を Modified に設定するようコンテキストに指示できます。 次に例を示します。

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 が呼び出されたときに、すべてのプロパティ値がデータベースに送信されます。

アタッチされるエンティティに、まだ追跡されていない他のエンティティへの参照が含まれている場合、これらの新しいエンティティは、Unchanged 状態でコンテキストにアタッチされます。自動的に Modified に設定されることはありません。 Modified とマークする必要があるエンティティが複数ある場合は、これらの各エンティティの状態を個別に設定する必要があります。

追跡されているエンティティの状態を変更する

既に追跡されているエンティティの状態を変更するには、そのエントリに 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 を呼び出すことで、エンティティの状態を変更することもできます。 たとえば、現在 Added 状態であるエンティティに対して Attach を呼び出すと、状態が Unchanged に変更されます。

挿入または更新パターン

一部のアプリケーションの一般的なパターンとして、主キーの値に応じて、エンティティを新規として追加するか (データベースへの挿入)、エンティティを既存としてアタッチし、変更済みとしてマークします (データベースの更新)。 たとえば、データベースで生成された整数の主キーを使用する場合、キーが 0 のエンティティを新規として扱い、キーが 0 以外のエンティティを既存として扱うのが一般的です。 このパターンは、主キーの値のチェックに基づいてエンティティの状態を設定することで実現できます。 次に例を示します。

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 が呼び出されたときに、すべてのプロパティ値がデータベースに送信されます。