2015-10-10 10 views
7

Załóżmy, że masz te klasy w swoich jednostkach.Czy to naprawdę niemożliwe, aby zaktualizować kolekcję dzieci w EF po wyjęciu z pudełka (inaczej nie-hacky)?

public class Parent 
{ 
    public int ParentID { get; set; } 
    public virtual ICollection<Child> Children { get; set; } 
} 

public class Child 
{ 
    public int ChildID { get; set; } 
    public int ParentID { get; set; } 
    public virtual Parent Parent { get; set; } 
} 

i masz interfejs użytkownika do aktualizacji Parent wraz z jego Children, co oznacza, jeśli użytkownik dodawać nowe Child potem trzeba włożyć, jeśli użytkownik edytuje istniejący Child to trzeba zaktualizować, a jeśli użytkownik usuwa Child, a następnie musisz go usunąć. Teraz oczywiście jeśli użyć następującego kodu

public void Update(Parent obj) 
{ 
    _parent.Attach(obj); 
    _dbContext.Entry(obj).State = EntityState.Modified; 
    _dbContext.SaveChanges(); 
} 

nie będzie w stanie wykryć zmiany wewnątrz Child ponieważ EF nie może wykryć zmiany wewnątrz Nieruchomość nawigacji.

Zadaję to pytanie 4 razy i otrzymuję mieszane odpowiedzi. Czy rzeczywiście można to zrobić bez komplikacji? Ten problem może rozwiązać problem, oddzielając interfejs użytkownika między Parent i Child, ale nie chcę, ponieważ łączenie zarówno Child i Parent w jednym menu jest dość powszechne w tworzeniu aplikacji biznesowych i bardziej przyjazne dla użytkownika.

AKTUALIZACJA: Próbuję rozwiązania poniżej, ale to nie działa.

public ActionResult(ParentViewModel model) 
{ 
    var parentFromDB = context.Parent.Get(model.ParentID); 

    if (parentFromDB != null) 
    { 
     parentFromDB.Childs = model.Childs; 
    } 

    context.SaveChanges(); 
} 

Zamiast wykrywania zmian wewnątrz dzieci, EF nie będzie w stanie powiedzieć, co zrobić ze starym dziecka. Na przykład, jeśli parentFromDB ma 3 dzieci przy pierwszym wyciągnięciu go z DB, to usuwam drugie i trzecie dziecko. Otrzymuję The relationship could not be changed because one or more of the foreign-key properties is non-nullable podczas zapisywania.

Wierzę, że to, co się stało: The relationship could not be changed because one or more of the foreign-key properties is non-nullable

Który zabrał mnie z powrotem do punktu wyjścia, bo w moim scenariuszu, nie mogę po prostu pobrać z DB i zaktualizować wpis i zadzwonić SaveChanges.

+0

Nie wiem, czy mam źle pytanie. Czy wyłączenie śledzenia zmian jest wymagane? W przeciwnym razie można to łatwo osiągnąć dzięki śledzeniu zmian EF. Dzięki śledzeniu zmian nie musisz jawnie ustawiać stanu obiektów, EF zrobi to za Ciebie, więc wszelkie modyfikacje wprowadzone do kolekcji Childs, w tym modyfikacje elementów kolekcji, będą automatycznie uwzględniane w zestawie zmian po zatwierdzeniu kontekstu (SaveChanges). –

+0

Zgadzam się z komentarzem odyss-jii, ale chcę dodać do niego dodatkowe informacje: oczywiście działa to tylko wtedy, gdy utrzymujesz kontekst otwarty między odszukiwaniem a zmianą właściwości z powrotem. w przeciwnym razie będziesz musiał sam ustawić swój byt podmiotu, a do tego będziesz musiał najpierw uzyskać wszystko w kontekście, więc musisz utworzyć wpis dla każdego obiektu i ustawić jego stan. – DevilSuichiro

+0

możesz być bardziej konkretny? Opublikuj kod, jeśli to możliwe. To właśnie mam na myśli (nie mogę znaleźć wersji EF6). http://www.entityframeworktutorial.net/EntityFramework4.3/update-one-to-many-entity-using-dbcontext.aspx – warheat1990

Odpowiedz

1

To sprawia, że ​​robisz to dziwnie.

Wymaga to leniwy załadunku na uzyskanie Childs (oczywiście modyfikować w trakcie pracy)

// dostać rodzica

var parent = context.Parent.Where(x => x.Id == parentId).SingleOrDefault(); 

napisał całą metodę testową dla Ciebie. (Mają zastosowanie do danego przypadku)

EmailMessage (rodzic) jest rodzicem i ma żaden lub wiele EmailAttachment za (dziecka)

[TestMethod] 
    public void TestMethodParentChild() 
    { 
     using (var context = new MyContext()) 
     { 
      //put some data in the Db which is linked 
      //--------------------------------- 
      var emailMessage = new EmailMessage 
      { 
       FromEmailAddress = "sss", 
       Message = "test", 
       Content = "hiehdue", 
       ReceivedDateTime = DateTime.Now, 
       CreateOn = DateTime.Now 
      }; 
      var emailAttachment = new EmailAttachment 
      { 
       EmailMessageId = 123, 
       OrginalFileName = "samefilename", 
       ContentLength = 3, 
       File = new byte[123] 
      }; 
      emailMessage.EmailAttachments.Add(emailAttachment); 
      context.EmailMessages.Add(emailMessage); 
      context.SaveChanges(); 
      //--------------------------------- 


      var firstEmail = context.EmailMessages.FirstOrDefault(x => x.Content == "hiehdue"); 
      if (firstEmail != null) 
      { 
       //change the parent if you want 

       //foreach child change if you want 
       foreach (var item in firstEmail.EmailAttachments) 
       { 
        item.OrginalFileName = "I am the shit"; 
       } 
      } 
      context.SaveChanges(); 


     } 
    } 

Aktualizacja

Wykonać AutoMappper Stuff ... jak powiedziałeś w swoim komentarzu.

Następnie, gdy jesteś gotowy, aby zapisać i masz go z powrotem jako poprawne typy, tj. Raz, który reprezentuje byty (Db), to zrobić.

var modelParent= "Some auto mapper magic to get back to Db types." 

var parent = context.Parent.FirstOrDefault(x => x.Id == modelParent.Id); 
//use automapper here to update the parent again 

if (parent != null) 
{ 
    parent.Childs = modelParent.Childs; 
} 
//this will update all childs ie if its not in the new list from the return 
//it will automatically be deleted, if its new it will be added and if it 
// exists it will be updated. 
context.SaveChanges(); 
+0

W moim przypadku dostałbym istniejący 'EmailMessage' wraz z jego właściwościami aka' EmailAttachment' z kontekstu i użyłem 'AutoMapera', aby odwzorować go na mój' EmailMessageViewModel' (ten ViewModel ma właściwość 1: 1 z 'EmailMessage'). Następnie mapuję 'EmailMessageViewModel' z powrotem na nowy obiekt' EmailMessage'. Ten nowy "EmailMessage" może mieć zmodyfikowany/nowy/usunięty "EmailAttachment". To jest, gdy nie mam pojęcia, jak zaktualizować go w kontekście. Mój oryginalny wpis: http://stackoverflow.com/questions/32964990/confuse-about-tracking-in-ef-updating-entity- with-child-collection – warheat1990

+0

Łatwo, ponownie pobierz oryginał ... i użyj automappera aby zmienić właściwość Jednostki (Rodzica), która jest postrzegana jako dołączona do kontekstu. Następnie ustaw Childs z pobranej jednostki na nowe childs. na przykład. Parent.Childs = Modified Childs. EF jest na tyle sprytny, aby wszystko dla ciebie zrobić ... dlatego EF jest świetny. – Seabizkit

+0

OK, ale co z nowo dodanym załącznikiem lub usuniętym załącznikiem? Do tej pory biorę to jako: 1. Pobierz oryginał 2. Porównaj z 'EmailMessageViewModel' 3. Ustaw' EntityState' na 'Modified', jeśli znaleziono ten sam identyfikator,' Added' if not found i 'Deleted 'jeśli znaleziono w pobranej wersji, ale nie w' EmailMessageViewModel' Czy to działa? – warheat1990

7

ponieważ EF nie może wykryć zmiany wewnątrz Nawigacji Własności

To wydaje się być nieco zniekształcony opis faktu, że _dbContext.Entry(obj).State = EntityState.Modified nie oznacza właściwości navigaton jako zmodyfikowane.

Oczywiście EF śledzi zmiany we właściwościach nawigacji. Śledzi zmiany właściwości i powiązań wszystkich elementów, które są dołączone do kontekstu. Dlatego odpowiedź na pytanie, teraz pozytywnie stwierdził ...

Czy jest możliwe aby zaktualizować kolekcję dziecięcą w EF wyjęciu z pudełka

... jest: tak.

Jedyna rzecz: nie robisz tego po wyjęciu z pudełka.

"out of the box" sposób zaktualizować każdy podmiot, czy to rodzic lub dziecko w jakiejś kolekcji jest:

  • Fetch podmioty z bazy danych.
  • Zmodyfikuj właściwości lub dodaj/usuń elementy do swoich kolekcji.
  • Zadzwoń pod SaveChanges().

To wszystko. Ef śledzi zmiany i nigdy nie ustawiasz jawnie obiektu State.

Jednak w scenariuszu odłączonym (n-warstwowym) staje się to bardziej skomplikowane. Dokonujemy serializacji i deserializacji obiektów, więc nie może istnieć żaden kontekst śledzący ich zmiany. Jeśli chcemy przechowywać jednostki w bazie danych, teraz naszym zadaniem jest, aby EF znał zmiany. Istnieją dwa sposoby, aby to zrobić:

  • Ustaw stany ręcznie, na podstawie tego, co wiemy o podmiotach (jak: klucz podstawowy> 0 oznacza, że ​​one istnieją i powinny być aktualizowane)
  • Farba stan: pobierz jednostki z bazy danych i ponownie zastosuj do nich zmiany z postaci szeregowych.

Jeśli chodzi o skojarzenia, zawsze musimy namalować stan. Musimy pobrać aktualne jednostki z bazy danych i ustalić, które dzieci zostały dodane/usunięte. Nie można tego wywnioskować z samego deserializowanego wykresu obiektów.

Istnieje wiele sposobów na złagodzenie tego nudnego i skomplikowanego zadania malowania państwa, ale to wykracza poza zakres tego Q & A.Niektóre referencje: