Ngoài (đối với câu trả lời được đánh dấu), có một sự khác biệt quan trọng giữa context.Entry(entity).State = EntityState.Unchanged
và context.Attach(entity)
(trong EF Core):
Tôi đã thực hiện một số thử nghiệm để tự mình hiểu nó nhiều hơn (do đó điều này cũng bao gồm một số thử nghiệm tham chiếu chung), vì vậy đây là kịch bản thử nghiệm của tôi:
- Tôi đã sử dụng EF Core 3.1.3
- Tôi đã sử dụng
QueryTrackingBehavior.NoTracking
- Tôi chỉ sử dụng các thuộc tính để lập bản đồ (xem bên dưới)
- Tôi đã sử dụng các ngữ cảnh khác nhau để nhận đơn hàng và cập nhật đơn hàng
- Tôi đã xóa toàn bộ db cho mọi bài kiểm tra
Đây là các mô hình:
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; }
}
Đây là dữ liệu thử nghiệm (gốc) trong cơ sở dữ liệu:
Để nhận đơn đặt hàng:
order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();
Bây giờ các bài kiểm tra:
Cập nhật đơn giản với 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
Cập nhật đơn giản với Đính kèm :
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
Cập nhật với việc thay đổi Id con với EntityState :
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
Cập nhật với việc thay đổi Id trẻ em với Đính kèm :
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.)
Lưu ý: Điều này ném Ngoại lệ, bất kể Id đã được thay đổi hoặc được đặt thành giá trị ban đầu, có vẻ như trạng thái của Id được đặt thành "đã thay đổi" và điều này không được phép (vì đó là khóa chính)
Cập nhật với việc thay đổi Id trẻ em như mới (không có sự khác biệt giữa EntityState và 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
Lưu ý: Xem sự khác biệt đối với Bản cập nhật với EntityState mà không có bản mới (ở trên). Lần này Tên sẽ được cập nhật, vì phiên bản Người dùng mới.
Cập nhật bằng cách thay đổi các Id tham chiếu với EntityState :
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
Cập nhật với việc thay đổi các Id Tham chiếu với Đính kèm :
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
Lưu ý: Các tài liệu tham khảo sẽ được thay đổi cho người dùng 3, nhưng cũng cho người dùng 1 sẽ được cập nhật, tôi đoán đây là do order.OrderedByUser.Id
không thay đổi (nó vẫn còn 1).
Kết luận
Với EntityState, bạn có nhiều quyền kiểm soát hơn, nhưng bạn phải tự mình cập nhật các thuộc tính phụ (cấp hai). Với Attach, bạn có thể cập nhật mọi thứ (tôi đoán với tất cả các cấp thuộc tính), nhưng bạn phải theo dõi các tài liệu tham khảo. Ví dụ: Nếu Người dùng (Người dùng đặt hàng) sẽ là một dropDown, thì việc thay đổi giá trị thông qua dropDown có thể ghi đè toàn bộ đối tượng Người dùng. Trong trường hợp này, dropDown-Value ban đầu sẽ bị ghi đè thay vì tham chiếu.
Đối với tôi trường hợp tốt nhất là đặt các đối tượng như OrderedByUser thành null và chỉ đặt order.OrderedByUserId thành giá trị mới, nếu tôi chỉ muốn thay đổi tham chiếu (bất kể là EntityState hay Attach).
Hy vọng điều này sẽ giúp ích, tôi biết nó là rất nhiều văn bản: D