切り離されたシナリオで、クライアントにdtoを取得し、エンティティにマップして保存すると、次のようになります。
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
その時は何ですか DbSet.Attach(entity)
または、EntityState.Modifiedが既にエンティティをアタッチしているときに.Attachメソッドを使用する必要があるのはなぜですか?
切り離されたシナリオで、クライアントにdtoを取得し、エンティティにマップして保存すると、次のようになります。
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
その時は何ですか DbSet.Attach(entity)
または、EntityState.Modifiedが既にエンティティをアタッチしているときに.Attachメソッドを使用する必要があるのはなぜですか?
回答:
するとcontext.Entry(entity).State = EntityState.Modified;
、エンティティをにアタッチするだけでなく、エンティティDbContext
全体をダーティとしてマークすることになります。つまり、実行するとcontext.SaveChanges()
、EFはエンティティのすべてのフィールドを更新する更新ステートメントを生成します。
これは常に望ましいとは限りません。
一方、DbSet.Attach(entity)
エンティティをダーティとしてマークせずにコンテキストにアタッチします。することと同じですcontext.Entry(entity).State = EntityState.Unchanged;
この方法でアタッチする場合、エンティティのプロパティの更新を続行しない限り、次にを呼び出したときにcontext.SaveChanges()
、EFはこのエンティティのデータベース更新を生成しません。
エンティティの更新を計画している場合でも、エンティティに多くのプロパティ(db列)があり、いくつかのみを更新する場合は、を実行してDbSet.Attach(entity)
から、いくつかのプロパティのみを更新する方が有利な場合があります。更新する必要があります。この方法で行うと、EFからより効率的な更新ステートメントが生成されます。EFは変更されたプロパティのみを更新します(対照的にcontext.Entry(entity).State = EntityState.Modified;
、すべてのプロパティ/列が更新されます)。
関連ドキュメント:Add / Attach and Entity States。
コード例
次のエンティティがあるとします。
public class Person
{
public int Id { get; set; } // primary key
public string FirstName { get; set; }
public string LastName { get; set; }
}
コードが次のようになっている場合:
context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();
生成されるSQLは次のようになります。
UPDATE person
SET FirstName = 'whatever first name is',
LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.
上記のupdateステートメントが、実際に値を変更したかどうかに関係なく、すべての列を更新する方法に注意してください。
対照的に、コードが「通常の」アタッチを次のように使用する場合:
context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();
次に、生成される更新ステートメントは異なります。
UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.
ご覧のとおり、更新ステートメントは、エンティティをコンテキストにアタッチした後に実際に変更された値のみを更新します。テーブルの構造によっては、これがパフォーマンスにプラスの影響を与える可能性があります。
ここで、どちらのオプションが適しているかは、実行しようとしていることに完全に依存します。
WHERE
、主キーのみを含む句を使用して、同時実行性チェックを行わずに、前述のように動作します。同時実行チェックを行うには、列を同時実行トークンまたはrowVersionとして明示的に構成する必要があります。その場合、WHERE
句には、すべてのフィールドではなく、主キーと同時実行トークン列のみが含まれます。あなたのテストがそうでなければ、私はそれについて聞きたいです。
DbContext.Entry(person).CurrentValues
およびDbContext.Entry(person).OriginalValues
。
さらに(マークされた回答に加えて)と(EF Coreでは)の間に重要な違いがcontext.Entry(entity).State = EntityState.Unchanged
ありcontext.Attach(entity)
ます。
私は自分でそれをより理解するためにいくつかのテストを行ったので(これにはいくつかの一般的な参照テストも含まれています)、これが私のテストシナリオです。
QueryTrackingBehavior.NoTracking
これらはモデルです:
public class Order
{
public int Id { get; set; }
public string Comment { get; set; }
public string ShippingAddress { get; set; }
public DateTime? OrderDate { get; set; }
public List<OrderPos> OrderPositions { get; set; }
[ForeignKey("OrderedByUserId")]
public User OrderedByUser { get; set; }
public int? OrderedByUserId { get; set; }
}
public class OrderPos
{
public int Id { get; set; }
public string ArticleNo { get; set; }
public int Quantity { get; set; }
[ForeignKey("OrderId")]
public Order Order { get; set; }
public int? OrderId { get; set; }
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
注文を取得するには:
order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();
今テスト:
EntityStateを使用した単純な更新:
db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
アタッチを使用した単純な更新:
db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1
EntityStateを使用して子IDを変更して更新します。
db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.Id = 3; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
Attachで子IDを変更して更新します。
db.Attach(order);
order.ShippingAddress = "Germany"; // would be UPDATED
order.OrderedByUser.Id = 3; // will throw EXCEPTION
order.OrderedByUser.FirstName = "William (CHANGED)"; // would be UPDATED
order.OrderPositions[0].Id = 3; // will throw EXCEPTION
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // would be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // would be INSERTED
db.SaveChanges();
// Throws Exception: The property 'Id' on entity type 'User' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.)
注:これは、Idが変更されたか、元の値に設定されたかに関係なく、例外をスローします。Idの状態が「変更済み」に設定されているようで、これは許可されていません(主キーであるため)
子IDを新しいものに変更して更新します(EntityStateとAttachの間に違いはありません):
db.Attach(order); // or db.Entry(order).State = EntityState.Unchanged;
order.OrderedByUser = new User();
order.OrderedByUser.Id = 3; // // Reference will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on User 3)
db.SaveChanges();
// Will generate SQL in 2 Calls:
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 3
注:新規(上記)なしのEntityStateを使用した更新との違いを参照してください。今回は、新しいUserインスタンスのため、名前が更新されます。
EntityStateで Reference-Idを変更して更新します。
db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.Id = 2; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
Attachで Reference-Idを変更して更新します。
db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on FIRST User!)
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1
注意:参照は、ユーザー3に変更されますが、また、利用者1が更新されます、私はので、これがあると思いますorder.OrderedByUser.Id
(それはまだ1です)変更されません。
結論 EntityStateを使用すると、より詳細に制御できますが、サブプロパティ(第2レベル)を自分で更新する必要があります。Attachを使用すると、すべてを更新できます(すべてのレベルのプロパティを使用していると思います)が、参照を監視する必要があります。例:User(OrderedByUser)がdropDownの場合、dropDownを介して値を変更すると、Userオブジェクト全体が上書きされる可能性があります。この場合、参照ではなく元のdropDown-Valueが上書きされます。
私にとって最良のケースは、OrderedByUserなどのオブジェクトをnullに設定し、(EntityStateまたはAttachに関係なく)参照のみを変更する場合にのみ、order.OrderedByUserIdを新しい値に設定することです。
これが役に立てば幸い、私はそれがたくさんのテキストであることを知っています:D