Nur zusätzlich (zu der markierten Antwort) gibt es einen wichtigen Unterschied zwischen context.Entry(entity).State = EntityState.Unchanged
und context.Attach(entity)
(in EF Core):
Ich habe einige Tests durchgeführt, um es selbst besser zu verstehen (daher umfasst dies auch einige allgemeine Referenztests). Dies ist also mein Testszenario:
- Ich habe EF Core 3.1.3 verwendet
- ich benutzte
QueryTrackingBehavior.NoTracking
- Ich habe nur Attribute für die Zuordnung verwendet (siehe unten)
- Ich habe verschiedene Kontexte verwendet, um die Bestellung zu erhalten und die Bestellung zu aktualisieren
- Ich habe die ganze Datenbank für jeden Test abgewischt
Dies sind die Modelle:
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; }
}
Dies sind die (ursprünglichen) Testdaten in der Datenbank:
So erhalten Sie die Bestellung:
order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();
Nun die Tests:
Einfaches Update mit 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
Einfaches Update mit Anhängen :
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
Update mit Änderung der untergeordneten IDs mit 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
Update mit Änderung der untergeordneten IDs mit Anhängen :
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.)
Hinweis: Dies löst eine Ausnahme aus, unabhängig davon, ob die ID geändert oder auf den ursprünglichen Wert gesetzt wurde. Der Status der ID scheint auf "geändert" gesetzt zu sein, und dies ist nicht zulässig (da es sich um den Primärschlüssel handelt).
Update mit Änderung der untergeordneten IDs als neu (kein Unterschied zwischen EntityState und 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
Hinweis: Sehen Sie den Unterschied zum Update mit EntityState ohne neues (oben). Dieses Mal wird der Name aufgrund der neuen Benutzerinstanz aktualisiert.
Update mit Änderung der Referenz- IDs mit 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
Update mit Änderung der Referenz-IDs mit Anhängen :
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
Hinweis: Die Referenz wird in Benutzer 3 geändert, aber auch Benutzer 1 wird aktualisiert. Ich denke, dies liegt daran, dass der order.OrderedByUser.Id
unverändert ist (es ist immer noch 1).
Fazit
Mit EntityState haben Sie mehr Kontrolle, müssen jedoch die Untereigenschaften (zweite Ebene) selbst aktualisieren. Mit Anhängen können Sie alles aktualisieren (ich denke mit allen Eigenschaftenebenen), aber Sie müssen die Referenzen im Auge behalten. Nur zum Beispiel: Wenn User (OrderedByUser) ein DropDown wäre, könnte das Ändern des Werts über ein DropDown das gesamte User-Objekt überschreiben. In diesem Fall würde der ursprüngliche dropDown-Wert anstelle der Referenz überschrieben.
Für mich ist der beste Fall, Objekte wie OrderedByUser auf null zu setzen und nur die order.OrderedByUserId auf den neuen Wert zu setzen, wenn ich nur die Referenz ändern möchte (egal ob EntityState oder Attach).
Hoffe das hilft, ich weiß es ist viel Text: D.