นอกจากนี้ (สำหรับคำตอบที่ทำเครื่องหมายไว้) ยังมีความแตกต่างที่สำคัญระหว่างcontext.Entry(entity).State = EntityState.Unchanged
และcontext.Attach(entity)
(ใน EF Core):
ฉันได้ทำการทดสอบเพื่อทำความเข้าใจมันมากขึ้นด้วยตัวเอง (ดังนั้นจึงรวมถึงการทดสอบอ้างอิงทั่วไปด้วย) ดังนั้นนี่คือสถานการณ์ทดสอบของฉัน:
- ฉันใช้ EF Core 3.1.3
- ฉันใช้
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
อัปเดตด้วยการเปลี่ยน Child-Ids ด้วย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
อัปเดตด้วยการเปลี่ยนรหัสเด็กพร้อมไฟล์แนบ :
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 จะถูกตั้งค่าเป็น "เปลี่ยนแปลง" และไม่อนุญาตให้ใช้สิ่งนี้ (เนื่องจากเป็นคีย์หลัก)
อัปเดตด้วยการเปลี่ยน Child-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 ที่ไม่มีใหม่ (ด้านบน) คราวนี้ชื่อจะได้รับการอัปเดตเนื่องจากอินสแตนซ์ผู้ใช้ใหม่
อัปเดตด้วยการเปลี่ยนรหัสอ้างอิงด้วย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
อัปเดตด้วยการเปลี่ยนรหัสอ้างอิงพร้อมไฟล์แนบ :
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 คุณสามารถควบคุมได้มากขึ้น แต่คุณต้องอัปเดตคุณสมบัติย่อย (ระดับที่สอง) ด้วยตัวเอง ด้วย Attach คุณสามารถอัปเดตทุกอย่างได้ (ฉันเดาว่ามีคุณสมบัติทุกระดับ) แต่คุณต้องจับตาดูการอ้างอิง ตัวอย่างเช่น: ถ้า User (OrderByUser) เป็น dropDown การเปลี่ยนค่าผ่าน dropDown อาจถูกเขียนทับทั้ง User-object ในกรณีนี้ dropDown-Value ดั้งเดิมจะถูกเขียนทับแทนการอ้างอิง
สำหรับฉันกรณีที่ดีที่สุดคือการตั้งค่าวัตถุเช่น OrderByUser เป็น null และตั้งค่าคำสั่งเท่านั้น OrderByUserId เป็นค่าใหม่ถ้าฉันต้องการเปลี่ยนการอ้างอิงเท่านั้น (ไม่ว่า EntityState หรือ Attach)
หวังว่านี่จะช่วยได้ฉันรู้ว่ามันมีข้อความมากมาย: D