Ekli bir nesneyi ayrı ama eşit bir nesne kullanarak güncelleyebilir miyim?


10

Film verilerini harici bir API'dan alıyorum. İlk aşamada her filmi kazımak ve kendi veritabanına yerleştirmek istiyorum. İkinci aşamada, hangi filmlerin bilgilerinin değiştiğini görmek için sorgulayabileceğim API'nın "Değişiklikler" API'sını kullanarak veritabanımı düzenli olarak güncelleyeceğim.

ORM katmanım Entity-Framework. Movie sınıfı şuna benzer:

class Movie
{
    public virtual ICollection<Language> SpokenLanguages { get; set; }
    public virtual ICollection<Genre> Genres { get; set; }
    public virtual ICollection<Keyword> Keywords { get; set; }
}

Sorun, güncellenmesi gereken bir filmim olduğunda ortaya çıkar: veritabanım izlenen nesneyi ve güncelleme API çağrısından aldığım yeni nesneyi göz ardı ederek farklı nesneler olarak düşünecektir .Equals().

Bu, soruna neden olur, çünkü veritabanını güncelleştirilmiş filmle güncellemeye çalıştığımda, varolan Filmi güncellemek yerine onu ekleyecektir.

Bu sorunu daha önce dillerle yaşadım ve çözümüm ekli dil nesnelerini aramak, onları bağlamdan ayırmak, PK'larını güncellenmiş nesneye taşımak ve bunu bağlama eklemekti. Ne zaman SaveChanges()şimdi yürütülür, aslında değiştirecektir.

Bu oldukça kokulu bir yaklaşım çünkü nesneme bu yaklaşıma devam Movieedersem, filmi, dilleri, türleri ve anahtar kelimeleri ayırmam, veritabanındaki her birini aramam, kimliklerini aktarmam ve yeni nesneler.

Bunu daha zarif yapmanın bir yolu var mı? İdeal olarak sadece güncellenmiş filmi içeriğe geçirmek ve Equals()yönteme göre güncellemek için doğru filmi seçmesini, tüm alanlarını ve her karmaşık nesne için güncellemesini istiyorum: mevcut Equals()yöntemi kendi yöntemine göre tekrar kullanın ve henüz mevcut değil.

.Update()Ekli tüm nesneleri alma kombinasyonunda kullanabileceğim her karmaşık nesne üzerinde yöntemler sağlayarak ayırma / ekleme işlemini atlayabilirim, ancak yine de güncelleştirmek için mevcut her nesneyi almamı gerektirir.


İzlenen varlığı neden API verilerinizden güncelleyemiyor ve varlıkları değiştirmeden / ayırmadan / eşleştirerek / eklemeden değişiklikleri kaydedemiyorsunuz?
Si-N

@ Si-N: Bunun tam olarak nasıl gideceğini genişletebilir misiniz?
Jeroen Vannevel

Tamam, son paragrafın daha anlamlı olduğunu eklediniz, varlıkları güncellemeden önce almaktan kaçınmaya mı çalışıyorsunuz? Film sınıfınızda PK'niz olabilecek hiçbir şey yok mu? Harici API'den filmleri varlıklarınızla nasıl eşleştiriyorsunuz? Yine de bir db çağrısında güncellemeniz gereken tüm varlıkları geri alabilirsiniz, bu büyük bir performans isabetine neden olmaması gereken daha basit bir çözüm olmaz mı (güncellemek için çok sayıda filmden bahsedmiyorsanız)?
Si-N

Movie sınıfımın bir PK'si var idve harici API'den gelen filmler alan kullanılarak yerel olanlarla eşleştiriliyor tmdbid. Filmler, türler, diller, anahtar kelimeler vb. İle ilgili olduğu için tek bir çağrıda güncellenmesi gereken tüm varlıkları alamıyorum. Bunların her birinin bir PK'si var ve veritabanında zaten var olabilir.
Jeroen Vannevel

Yanıtlar:


8

Ne umduğumu bulamadım ama mevcut select-detach-update-attach dizisi üzerinde bir gelişme buldum.

Uzantı yöntemi AddOrUpdate(this DbSet), tam olarak ne yapmak istediğimi yapmanıza izin verir: Orada değilse ekleyin ve mevcut bir değer bulduğunda güncelleyin. Bunu sadece daha önce kullanmadığımı fark etmedim çünkü gerçekten sadece Göçlerle birlikte seed()yöntemde kullanıldığını gördüm . Bunu kullanmamam için bir sebep varsa, bana bildirin.

Dikkat edilmesi gereken bir şey: Eşitliğin nasıl belirleneceğini özel olarak seçmenize izin veren bir aşırı yük var. Burada benim kullanmış olabilirim TMDbIdama onun yerine kendi kimliğimi tamamen göz ardı etmeyi ve TMDbId'de bir PK kullanmayı tercih ettim DatabaseGeneratedOption.None. Bu yaklaşımı, uygun olduğunda, her bir alt toplamada da kullanıyorum.

Kaynağın ilginç kısmı :

internalSet.InternalContext.Owner.Entry(existing).CurrentValues.SetValues(entity);

veriler bu şekilde gerçekte güncellenir.

Geriye sadece AddOrUpdatebundan etkilenmesini istediğiniz her nesne çağırıyor :

public void InsertOrUpdate(Movie movie)
{
    _context.Movies.AddOrUpdate(movie);
    _context.Languages.AddOrUpdate(movie.SpokenLanguages.ToArray());
    // Other objects/collections
    _context.SaveChanges();
}

Nesnemin güncellenmesi gereken her parçasını manuel olarak belirtmem gerektiğinden umduğum kadar temiz değil, ancak alacağı kadar yakın.

İlgili okuma: /programming/15336248/entity-framework-5-updating-a-record


Güncelleme:

Testlerim yeterince titiz değildi. Bu tekniği kullandıktan sonra yeni dil eklenirken filme bağlı olmadığını fark ettim. çoktan çoğa tabloda. Bu bilinen ama görünüşte düşük öncelikli bir konudur ve bildiğim kadarıyla düzeltilmemiştir.

Sonunda, Update(T)her tipte yöntemlere sahip olduğum yaklaşıma gitmeye karar verdim ve bu olay sırasını takip ettim :

  • Yeni nesnede koleksiyonların üzerinden geçin
  • Her koleksiyondaki her giriş için veritabanına bakın
  • Varsa Update(), yeni değerlerle güncellemek için yöntemi kullanın
  • Eğer yoksa, uygun DbSet'e ekleyin
  • Ekli nesneleri döndürün ve kök nesnedeki koleksiyonları ekli nesnelerin koleksiyonlarıyla değiştirin.
  • Kök nesneyi bulma ve güncelleme

Çok fazla manuel iş var ve çirkin, bu yüzden birkaç daha yeniden düzenleme yapacağım, ancak şimdi testlerim bunun daha titiz senaryolar için çalışması gerektiğini gösteriyor.


Daha sonra temizledikten sonra şimdi bu yöntemi kullanıyorum:

private IEnumerable<T> InsertOrUpdate<T, TKey>(IEnumerable<T> entities, Func<T, TKey> idExpression) where T : class
{
    foreach (var entity in entities)
    {
        var existingEntity = _context.Set<T>().Find(idExpression(entity));
        if (existingEntity != null)
        {
            _context.Entry(existingEntity).CurrentValues.SetValues(entity);
            yield return existingEntity;
        }
        else
        {
            _context.Set<T>().Add(entity);
            yield return entity;
        }
    }
    _context.SaveChanges();
}

Bu, onu böyle aramamı ve altta yatan koleksiyonları eklememi / güncellememi sağlar:

movie.Genres = new List<Genre>(InsertOrUpdate(movie.Genres, x => x.TmdbId));

Alınan değeri orijinal kök nesneye nasıl yeniden atadığımı fark et: şimdi her bir bağlı nesneye bağlı. Kök nesneyi (film) güncellemek aynı şekilde yapılır:

var localMovie = _context.Movies.SingleOrDefault(x => x.TmdbId == movie.TmdbId);
if (localMovie == null)
{
    _context.Movies.Add(movie);
} 
else
{
    _context.Entry(localMovie).CurrentValues.SetValues(movie);
}

1-M ilişkisindeki Silme işlemlerini nasıl gerçekleştiriyorsunuz? örneğin, 1-Filmin birden fazla dili olabilir; dillerden biri kaldırılırsa kodunuz onu kaldırıyor mu? Bu sizin çözüm sadece uçlar ve veya güncellemeleri görünür (ancak kaldırmaz?)
joedotnot

0

Farklı alanlarla uğraştığınızdan idve tmbidAPI'ları türler, diller, anahtar kelimeler vb.Gibi tüm bilgilerin tek ve ayrı bir dizinini oluşturacak şekilde güncellemenizi öneririm. Film sınıfınızdaki belirli bir nesne hakkındaki tüm bilgiler.


1
Burada düşünce trenini takip etmiyorum. Genişleyebilir misiniz? Harici API'nin tamamen benim kontrolüm dışında olduğunu unutmayın.
Jeroen Vannevel
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.