DbSet.Attach (varlık) - DbContext.Entry (varlık) .State = EntityState.Modified


115

Bağımsız bir senaryoda olduğumda ve istemciden onu kaydetmek için bir varlığa eşleştirdiğim bir dto aldığımda şunu yapıyorum:

context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();

O zaman ne için DbSet.Attach(entity)

veya EntityState.Modified varlığı zaten eklediğinde neden .Attach yöntemini kullanmalıyım?


Bazı sürüm bilgilerini eklemeniz daha iyi olur, bu daha önce sorulmuştur. Bunun yeni bir soruyu hak edip etmediği konusunda net değilim.
Henk Holterman

Yanıtlar:


278

Bunu yaptığınızda context.Entry(entity).State = EntityState.Modified;, yalnızca varlığı ona bağlamazsınız, DbContextaynı zamanda tüm varlığı kirli olarak işaretlersiniz. Bu, bunu yaptığınızda context.SaveChanges()EF'in varlığın tüm alanlarını güncelleyecek bir güncelleme bildirimi oluşturacağı anlamına gelir .

Bu her zaman istenmez.

Öte yandan, DbSet.Attach(entity)bağlama varlık verdiği olmadan kirli işaretleme. Yapmakla eşdeğerdircontext.Entry(entity).State = EntityState.Unchanged;

Bu şekilde iliştirirken, varlıktaki bir özelliği güncellemeye devam etmediğiniz sürece, bir sonraki çağrınızda context.SaveChanges()EF bu varlık için bir veritabanı güncellemesi oluşturmayacaktır.

Bir varlığa güncelleme yapmayı planlıyor olsanız bile, varlığın çok sayıda özelliği (db sütunu) varsa, ancak yalnızca birkaçını güncellemek istiyorsanız, a yapmak DbSet.Attach(entity)ve ardından yalnızca birkaç özelliği güncellemek avantajlı olabilir. güncellenmesi gerekiyor. Bu şekilde yapmak, EF'ten daha verimli bir güncelleme bildirimi oluşturacaktır. EF, yalnızca değiştirdiğiniz özellikleri günceller ( context.Entry(entity).State = EntityState.Modified;bunun tersine, tüm özelliklerin / sütunların güncellenmesine neden olur)

İlgili belgeler: Ekleme / Ekleme ve Varlık Durumları .

Kod örneği

Aşağıdaki varlığa sahip olduğunuzu varsayalım:

public class Person
{
    public int Id { get; set; } // primary key
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Kodunuz şöyle görünüyorsa:

context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();

Oluşturulan SQL şuna benzer:

UPDATE person
SET FirstName = 'whatever first name is',
    LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.

Değerleri gerçekten değiştirmiş olsanız da değiştirmemiş olsanız da, yukarıdaki güncelleme ifadesinin tüm sütunları nasıl güncelleyeceğine dikkat edin.

Buna karşılık, kodunuz "normal" Ekle'yi kullanıyorsa aşağıdaki gibi:

context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();

Ardından oluşturulan güncelleme ifadesi farklıdır:

UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.

Gördüğünüz gibi, update deyimi yalnızca siz varlığı bağlama ekledikten sonra gerçekten değiştirilen değerleri günceller. Masanızın yapısına bağlı olarak, bunun performans üzerinde olumlu bir etkisi olabilir.

Şimdi, hangi seçeneğin sizin için daha iyi olduğu tamamen ne yapmaya çalıştığınıza bağlıdır.


1
EF, WHERE yan tümcesini bu şekilde oluşturmaz. Yeni (yani yeni Varlık ()) ile oluşturulmuş bir varlık eklediyseniz ve onu değiştirilmiş olarak ayarlarsanız, iyimser kilit nedeniyle tüm orijinal alanları ayarlamanız gerekir. UPDATE sorgusunda oluşturulan WHERE yan tümcesi genellikle tüm orijinal alanları içerir (yalnızca kimliği değil), bu nedenle bunu yapmazsanız EF bir eşzamanlılık istisnası atar.
bubi

3
@budi: Geri bildiriminiz için teşekkür ederiz. Emin olmak için yeniden test ettim ve temel bir varlık için, WHEREyalnızca birincil anahtarı içeren madde ile ve herhangi bir eşzamanlılık kontrolü olmadan açıkladığım gibi davranıyor . Eşzamanlılık denetimine sahip olmak için, bir sütunu eşzamanlılık belirteci veya rowVersion olarak açıkça yapılandırmam gerekiyor. Bu durumda, WHEREyan tümce tüm alanları değil, yalnızca birincil anahtara ve eşzamanlılık belirteci sütununa sahip olacaktır. Testleriniz aksini gösteriyorsa, bunu duymak isterim.
sstan

cadı özelliğinin değiştirildiğini dinamik olarak nasıl bulabilirim?
Navid_pdp11

2
@ Navid_pdp11 DbContext.Entry(person).CurrentValuesve DbContext.Entry(person).OriginalValues.
Shimmy Weitzhandler

biraz konu dışı olabilir, ancak bir depo kalıbı kullanırsam, her model için db'ye yeni bir kayıt eklerken izlenmemiş durumda olması gereken bazı varlıklar olduğundan her model için bir havuz oluşturmalıyım, bu yüzden sahip olamam ekleme sırasında varlıkları bağlama ekleyen genel bir havuz. Bunu en iyi nasıl halletiyorsun?
jayasurya_j

3

DbSet.UpdateYöntemi kullandığınızda , Entity Framework varlığınızın tüm özelliklerini olarak işaretler EntityState.Modified, böylece onları izler. Tüm mülklerinizi değil, yalnızca bazı mülklerinizi değiştirmek istiyorsanız, kullanın DbSet.Attach. Bu yöntem tüm mülklerinizi oluşturur EntityState.Unchanged, bu nedenle güncellemek istediğiniz mülklerinizi yapmanız gerekir EntityState.Modified. Bu nedenle, uygulama isabet DbContext.SaveChangesettiğinde, yalnızca değiştirilmiş özellikleri çalıştıracaktır.


0

Sadece ek olarak (işaretli cevaba) ve (EF Core'da) arasında önemli bir fark vardır :context.Entry(entity).State = EntityState.Unchangedcontext.Attach(entity)

Kendi kendime daha iyi anlamak için bazı testler yaptım (bu nedenle bu aynı zamanda bazı genel referans testlerini de içerir), bu yüzden bu benim test senaryom:

  • EF Core 3.1.3 kullandım
  • kullandım QueryTrackingBehavior.NoTracking
  • Eşleme için sadece öznitelikler kullandım (aşağıya bakın)
  • Siparişi almak ve siparişi güncellemek için farklı bağlamlar kullandım
  • Her test için tüm verileri sildim

Bunlar modeller:

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

Bu, veritabanındaki (orijinal) test verileridir: görüntü açıklamasını buraya girin

Siparişi almak için:

order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();

Şimdi testler:

EntityState ile Basit Güncelleme :

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

Attach ile Basit Güncelleme :

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 ile Alt Kimliklerini değiştirerek güncelleme :

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 ile değiştirilen Child-Ids ile güncelleme :

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.)

Not: Bu, Id'nin değiştirilmiş veya orijinal değere ayarlanmış olması fark etmeksizin İstisna atar, Id durumu "değiştirildi" olarak ayarlanmış gibi görünür ve buna izin verilmiyor (çünkü bu birincil anahtar)

Alt Kimliklerini yeni olarak değiştirerek güncelleme (EntityState ve Attach arasında fark yok):

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

Not: Yeni olmadan EntityState ile Güncellemedeki farkı görün (yukarıda). Bu sefer Ad, yeni Kullanıcı örneği nedeniyle güncellenecektir.

Referans Kimliklerini EntityState ile değiştirerek güncelleme :

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 ile Referans Kimliklerini değiştirerek güncelleme :

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

Not: Referans Kullanıcı 3'e değişecektir, fakat aynı zamanda kullanıcı 1 güncellenir çünkü bu sanırım order.OrderedByUser.Id(hala 1 var) değişmez.

Sonuç EntityState ile daha fazla kontrole sahip olursunuz, ancak alt özellikleri (ikinci seviye) kendiniz güncellemeniz gerekir. Attach ile her şeyi güncelleyebilirsiniz (sanırım tüm özellik seviyelerinde), ancak referanslara dikkat etmeniz gerekir. Örneğin: Kullanıcı (OrderedByUser) bir dropDown ise, değeri bir dropDown aracılığıyla değiştirmek, tüm Kullanıcı nesnesinin üzerine yazılabilir. Bu durumda, referans yerine orijinal dropDown-Değerinin üzerine yazılır.

Benim için en iyi durum OrderedByUser gibi nesneleri null olarak ayarlamak ve yalnızca referansı değiştirmek istersem order.OrderedByUserId değerini yeni değere ayarlamaktır (EntityState veya Attach farketmez).

Umarım bu yardımcı olur, çok fazla metin olduğunu biliyorum: D

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.