EF4 POCO nesnelerinin değişikliklerini kaydederken ilişkileri güncelle


107

Entity Framework 4, POCO nesneleri ve ASP.Net MVC2. BlogPost ve Tag varlıkları arasında çoktan çoğa bir ilişkim var. Bu, T4 tarafından oluşturulan POCO BlogPost sınıfımda:

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

Bir BlogPost ve ilgili Etiketleri ObjectContext'in bir örneğinden istiyorum ve başka bir katmana gönderiyorum (MVC uygulamasında görüntüle). Daha sonra, değişen özellikler ve değişen ilişkilerle güncellenmiş BlogPost'u geri alıyorum. Örneğin "A" "B" ve "C" etiketleri vardı ve yeni etiketler "C" ve "D" dir. Benim özel örneğimde yeni Etiketler yoktur ve Etiketlerin özellikleri asla değişmez, bu nedenle kaydedilmesi gereken tek şey değişen ilişkilerdir. Şimdi bunu başka bir ObjectContext'e kaydetmem gerekiyor. (Güncelleme: Şimdi aynı bağlamda yapmaya çalıştım ve başarısız oldum.)

Sorun: İlişkileri düzgün bir şekilde kurtarmasını sağlayamıyorum. Bulduğum her şeyi denedim:

  • Controller.UpdateModel ve Controller. TryUpdateModel çalışmıyor.
  • Eski BlogPost'u bağlamdan almak ve ardından koleksiyonu değiştirmek işe yaramıyor. (sonraki noktadan itibaren farklı yöntemlerle)
  • Bu muhtemelen işe yarardı, ancak umarım bu çözüm değil, geçici bir çözümdür :(.
  • Her olası kombinasyonda BlogPost ve / veya Etiketler için Attach / Add / ChangeObjectState işlevleri denendi. Başarısız oldu.
  • Bu bakışlar ihtiyacım olanı gibi değil, ama (ben bunu düzeltmek için çalıştı, ancak benim sorun için olamaz) işi yapmaz.
  • Bağlamın ilişki nesneleri ChangeState / Add / Attach / ... denendi. Başarısız oldu.

"Çalışmıyor", çoğu durumda, hata oluşturmayana ve en azından BlogPost'un özelliklerini kaydedene kadar verilen "çözüm" üzerinde çalıştığım anlamına gelir. İlişkilerde ne olduğu değişir: genellikle Etiketler yeni PK'larla Etiket tablosuna yeniden eklenir ve kaydedilen BlogPost orijinal olanlara değil bunlara başvurur. Elbette döndürülen Etiketlerin PK'leri var ve kaydetme / güncelleme yöntemlerinden önce PK'leri kontrol ediyorum ve bunlar veritabanındakilere eşitler, bu nedenle muhtemelen EF bunların yeni nesneler olduğunu ve bu PK'lerin geçici olanlar olduğunu düşünüyor.

Bildiğim bir sorun ve otomatikleştirilmiş basit bir çözüm bulmayı imkansız hale getirebilir: Bir POCO nesnesinin koleksiyonu değiştirildiğinde, bu yukarıda belirtilen sanal koleksiyon özelliği tarafından gerçekleşmelidir, çünkü o zaman FixupCollection hilesi diğer uçtaki ters referansları güncelleyecektir. çoka çok ilişkisi. Ancak, Görünüm güncellenmiş bir BlogPost nesnesini "döndürdüğünde" bu gerçekleşmedi. Bu, belki sorunumun basit bir çözümü olmadığı anlamına gelir, ancak bu beni çok üzer ve EF4-POCO-MVC zaferinden nefret ederim :(. Ayrıca bu, EF'in bunu MVC ortamında hangisi olursa olsun yapamayacağı anlamına gelir. EF4 nesne türleri kullanılır :(. Anlık görüntü tabanlı değişiklik izlemenin, değiştirilen BlogPost'un mevcut PK'larla Etiketlerle ilişkisi olduğunu bulması gerektiğini düşünüyorum.

Btw: Aynı sorunun bire çok ilişkilerde de olduğunu düşünüyorum (google ve meslektaşım öyle söylüyor). Bunu evde deneyeceğim, ancak bu işe yarasa bile uygulamamdaki altı çoka çok ilişkimde bana yardımcı olmuyor :(.


Lütfen kodunuzu gönderin. Bu yaygın bir senaryodur.
John Farrell

1
Bu soruna otomatik bir çözümüm var, aşağıdaki cevaplarda gizlidir, bu yüzden çoğu kişi özleyecektir, ancak lütfen bir göz atın, çünkü size bir işten çok tasarruf
ettirir,

@brentmckendrick Başka bir yaklaşımın daha iyi olduğunu düşünüyorum. Tüm değiştirilmiş nesne grafiğini kablo üzerinden göndermek yerine neden deltayı göndermeyesiniz? Bu durumda oluşturulmuş DTO sınıflarına bile ihtiyacınız olmaz. Bu konuda herhangi bir fikriniz varsa, lütfen stackoverflow.com/questions/1344066/calculate-object-delta adresinde tartışalım .
HappyNomad

Yanıtlar:


145

Bunu şu şekilde deneyelim:

  • İçeriğe BlogPost ekleyin. Nesneyi bağlama nesnenin durumuna ekledikten sonra, tüm ilgili nesneler ve tüm ilişkiler Değiştirilmemiş olarak ayarlanır.
  • BlogPost'unuzu Değiştirilmiş olarak ayarlamak için context.ObjectStateManager.ChangeObjectState kullanın
  • Etiket koleksiyonunu yineleyin
  • Geçerli Etiket ile BlogPost arasındaki ilişkinin durumunu ayarlamak için context.ObjectStateManager.ChangeRelationshipState kullanın.
  • Değişiklikleri Kaydet

Düzenle:

Sanırım yorumlarımdan biri, EF'in sizin için birleştirme yapacağına dair yanlış bir umut verdi. Bu problemle çok oynadım ve sonucum EF'in bunu sizin için yapmayacağını söylüyor. Sanırım sorumu MSDN'de de buldunuz . Gerçekte internette bu tür pek çok soru var. Sorun, bu senaryo ile nasıl başa çıkılacağının açıkça belirtilmemesidir. Öyleyse soruna bir bakalım:

Sorun arka planı

EF'in varlıklar üzerindeki değişiklikleri izlemesi gerekir, böylece kalıcılık hangi kayıtların güncellenmesi, eklenmesi veya silinmesi gerektiğini bilir. Sorun, değişiklikleri takip etmenin ObjectContext sorumluluğunda olmasıdır. ObjectContext, yalnızca ekli varlıklar için değişiklikleri izleyebilir. ObjectContext dışında oluşturulan varlıklar hiç izlenmez.

Sorun Açıklaması

Yukarıdaki açıklamaya dayanarak, varlığın her zaman bağlama bağlı olduğu bağlantılı senaryolar için EF'nin daha uygun olduğunu açıkça belirtebiliriz - WinForm uygulaması için tipiktir. Web uygulamaları, bağlamın istek işlendikten sonra kapatıldığı ve varlık içeriğinin istemciye HTTP yanıtı olarak iletildiği bağlantısız senaryo gerektirir. Sonraki HTTP isteği, yeniden oluşturulması, yeni bağlama eklenmesi ve kalıcı olması gereken varlığın değiştirilmiş içeriğini sağlar. Rekreasyon genellikle bağlam kapsamı dışında gerçekleşir (sürekli görmezden gelme ile katmanlı mimari).

Çözüm

Öyleyse bu tür bağlantısız senaryo ile nasıl başa çıkılır? POCO sınıflarını kullanırken, değişiklik izleme ile başa çıkmak için 3 yolumuz var:

  • Anlık görüntü - aynı bağlam gerektirir = bağlantısız senaryo için işe yaramaz
  • Dinamik izleme proxy'leri - aynı bağlam gerektirir = bağlantısız senaryo için işe yaramaz
  • Manuel senkronizasyon.

Tek varlık üzerinde manuel senkronizasyon kolay bir iştir. Sadece varlık eklemeniz ve eklemek için AddObject öğesini çağırmanız, silmek için DeleteObject veya ObjectStateManager'daki durumu güncelleme için Modified olarak ayarlamanız gerekir. Gerçek acı, tek bir varlık yerine nesne grafiği ile uğraşmanız gerektiğinde ortaya çıkar. Bu acı, bağımsız derneklerle (Yabancı Anahtar özelliğini kullanmayanlar) ve pek çok ilişkiyle uğraşmanız gerektiğinde daha da kötüdür. Bu durumda nesne grafiğindeki her bir varlığı ve ayrıca nesne grafiğindeki her bir ilişkiyi manuel olarak senkronize etmeniz gerekir.

El ile eşitleme, MSDN dokümantasyonunda bir çözüm olarak önerilmiştir: Nesnelerin eklenmesi ve çıkarılması şunu söylüyor:

Nesneler, Değiştirilmemiş bir durumda nesne bağlamına eklenir. Nesnenizin bağımsız durumda değiştirildiğini bildiğiniz için bir nesnenin veya ilişkinin durumunu değiştirmeniz gerekirse, aşağıdaki yöntemlerden birini kullanın.

Bahsedilen yöntemler şunlardır: ChangeObjectState ve ChangeRelationshipState of ObjectStateManager = manuel değişiklik izleme. Benzer teklif, diğer MSDN dokümantasyon makalesindedir: İlişkileri Tanımlama ve Yönetme diyor ki:

Bağlantısı kesilmiş nesnelerle çalışıyorsanız, senkronizasyonu manuel olarak yönetmeniz gerekir.

Dahası, EF'in bu davranışını tam olarak eleştiren EF v1 ile ilgili bir blog yazısı var .

Çözüm nedeni

EF, Yenileme , Yükleme , GeçerliDeğerleri Uygula , Özgün Değerleri Uygula , Birleştirme Seçeneği vb. Gibi birçok "yararlı" işlem ve ayara sahiptir . Ancak araştırmamla, tüm bu özellikler yalnızca tek bir varlık için çalışır ve yalnızca skaler öncelikleri etkiler (= gezinme özelliklerini ve ilişkileri etkilemez). Bu yöntemleri varlıkta iç içe geçmiş karmaşık türlerle test etmemeyi tercih ederim.

Önerilen diğer çözüm

Gerçek Birleştirme işlevi yerine EF ekibi , sorunu çözmeyen Kendi Kendini İzleme Varlıkları (STE) adı verilen bir şey sağlar . İlk olarak STE, yalnızca tüm işlem için aynı örnek kullanıldığında çalışır. Web uygulamasında, örneği görünüm durumunda veya oturumda saklamadığınız sürece durum böyle değildir. Bundan dolayı EF kullanmaktan çok mutsuzum ve NHibernate'in özelliklerini kontrol edeceğim. İlk gözlem, NHibernate'in belki de böyle bir işlevselliğe sahip olduğunu söylüyor .

Sonuç

Bu varsayımları , MSDN forumundaki başka bir ilgili soruya tek bağlantıyla sonlandıracağım . Zeeshan Hirani'nin cevabına bakın. Entity Framework 4.0 Tariflerinin yazarıdır . Nesne grafiklerinin otomatik birleştirilmesinin desteklenmediğini söylüyorsa, ona inanıyorum.

Ancak yine de tamamen hatalı olma ihtimalim var ve EF'de bazı otomatik birleştirme işlevleri var.

Düzenleme 2:

Gördüğünüz gibi bu, 2007'de MS Connect'e öneri olarak eklenmişti . MS, sonraki sürümde yapılacak bir şey olarak onu kapattı ama aslında STE dışında bu boşluğu iyileştirmek için hiçbir şey yapılmadı.


7
Bu SO'da okuduğum en iyi cevaplardan biri. Konuyla ilgili bu kadar çok MSDN makalesinin, belgesinin ve blog gönderisinin anlaşılamadığını açıkça belirttiniz. EF4, "ayrılmış" varlıklardan ilişkileri güncellemeyi doğal olarak desteklemez. Yalnızca kendiniz uygulamanız için araçlar sağlar. Teşekkür ederim!
tyriker

1
Öyleyse geçtiğimiz birkaç ay sonra, NHibernate'in EF4'e kıyasla bu konuyla ilgili olmasına ne dersiniz?
CallMeLaNN

1
Bu NHibernate'de çok iyi destekleniyor :-) manuel olarak birleştirmeye gerek yok, benim örneğimde 3 seviyeli derin nesne grafiği, sorunun cevapları var, her cevabın yorumları ve soruların yorumları da var. NHibernate, ne kadar karmaşık olursa olsun, nesne grafiğinizi sürdürebilir / birleştirebilir, ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html Başka bir memnun NHibernate kullanıcısı: codinginstinct.com/2009/11/…
Michael Buen

2
Şimdiye kadar okuduğum en iyi açıklamalardan biri !! Çok teşekkürler
marvelTracker

2
EF ekibi, EF6 sonrası bu konuyu ele almayı planlıyor. Entityframework.codeplex.com/workitem/864
Eric J.

19

Yukarıda Ladislav tarafından açıklanan soruna bir çözümüm var. DbContext için, sağlanan grafiğin ve kalıcı grafiğin bir farkına dayalı olarak ekleme / güncelleme / silme işlemlerini otomatik olarak gerçekleştirecek bir uzantı yöntemi oluşturdum.

Şu anda Entity Framework kullanarak, kişilerin güncellemelerini manuel olarak gerçekleştirmeniz, her bir kişinin yeni olup olmadığını kontrol etmeniz ve eklemeniz, güncellenip güncellenmediğini kontrol etmeniz, kaldırılıp kaldırılmadığını kontrol edip veri tabanından silmeniz gerekecektir. Bunu büyük bir sistemdeki birkaç farklı agregalar için yapmanız gerektiğinde, daha iyi ve daha genel bir yol olması gerektiğini anlamaya başlarsınız.

Lütfen bir göz atın ve http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- yardımcı olup olamayacağını görün bağımsız varlıkların grafiği /

Doğrudan buradaki koda gidebilirsiniz https://github.com/refactorthis/GraphDiff


Eminim bu soruyu kolayca çözebilirsiniz , bununla kötü zaman geçiriyorum.
Shimmy Weitzhandler

1
Merhaba Shimmy, özür dilerim nihayet bakmak için biraz zamanım oldu. Bu gece onu inceleyeceğim.
brentmckendrick

Bu kütüphane harika ve bana ÇOK zaman kazandırdı! Teşekkürler!
lordjeb

9

OP için geç olduğunu biliyorum, ancak bu çok yaygın bir sorun olduğu için başka birine hizmet etmesi durumunda bunu gönderdim. Bu sorunla uğraşıyordum ve oldukça basit bir çözüm bulduğumu düşünüyorum, yaptığım şey:

  1. Durumunu Değiştirilmiş olarak ayarlayarak ana nesneyi (örneğin Bloglar) kaydedin.
  2. Güncellemem gereken koleksiyonlar da dahil olmak üzere güncellenmiş nesne için veritabanını sorgulayın.
  3. Koleksiyonumun dahil etmesini istediğim varlıkları sorgulayın ve .ToList () dönüştürün.
  4. Ana nesnenin koleksiyonunu 3. adımdan aldığım Listeye güncelleyin.
  5. Değişiklikleri Kaydet();

Aşağıdaki örnekte "dataobj" ve "_categories" denetleyicim tarafından alınan parametrelerdir "dataobj" benim ana nesnemdir ve "_categories" görünümde kullanıcının seçtiği kategorilerin kimliklerini içeren bir IEnumerable'dır.

    db.Entry(dataobj).State = EntityState.Modified;
    db.SaveChanges();
    dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
    var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
    dataobj.Categories = it;
    db.SaveChanges();

Çoklu ilişkiler için bile işe yarar


7

Entity Framework ekibi, bunun bir kullanılabilirlik sorunu olduğunun farkındadır ve EF6 sonrası bu sorunu gidermeyi planlamaktadır.

Entity Framework ekibinden:

Bu, farkında olduğumuz ve EF6 sonrası üzerinde daha fazla çalışma yapmayı düşündüğümüz ve planladığımız bir kullanılabilirlik sorunudur. Sorunu izlemek için bu çalışma öğesini oluşturdum: http://entityframework.codeplex.com/workitem/864 Çalışma öğesi ayrıca bunun için kullanıcının ses öğesine bir bağlantı içerir - varsa ona oy vermenizi tavsiye ederim zaten yapılmadı.

Bu sizi etkiliyorsa, özelliğe şu adresten oy verin:

http://entityframework.codeplex.com/workitem/864


EF6 sonrası? iyimser durumda o zaman hangi yıl olacak?
quetzalcoatl

@quetzalcoatl: En azından radarlarında :-) EF, EF 1'den bu yana çok uzun bir yol kat etti, ancak yine de gidecek bir yolu var.
Eric J.

1

Tüm yanıtlar sorunu açıklamak için harikaydı, ancak hiçbiri sorunu benim için gerçekten çözmedi.

Ana varlıktaki ilişkiyi kullanmadıysam, ancak alt varlıkları ekleyip kaldırırsam her şeyin yolunda gittiğini gördüm.

VB için özür dilerim ama çalıştığım proje bu şekilde yazılmıştır.

"Rapor" ana varlığı, "ReportRole" ile bire birçok ilişkiye sahiptir ve "ReportRoles" özelliğine sahiptir. Yeni roller, bir Ajax çağrısından virgülle ayrılmış bir dizeyle aktarılır.

İlk satır tüm alt varlıkları kaldıracak ve "db.ReportRoles.Remove (f)" yerine "report.ReportRoles.Remove (f)" kullanırsam hatayı alacağım.

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))
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.