2015-10-10 6 views
7

Diciamo che hai queste classi nelle tue entità.E 'davvero impossibile aggiornare la raccolta dei bambini in EF fuori dagli schemi (alias modo non 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; } 
} 

E si dispone di un'interfaccia utente per aggiornare il Parent insieme al suo Children, cioè se l'utente aggiungere nuove Child poi si deve inserire, se l'utente modifica un Child esistente allora avete bisogno di aggiornare e, se l'utente rimuove uno Child quindi è necessario eliminare. Ora, ovviamente, se si utilizza il seguente codice

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

non sarà in grado di rilevare i cambiamenti all'interno del Child perché EF non è in grado di rilevare i cambiamenti all'interno di una proprietà di navigazione.

Ho fatto questa domanda per come 4 volte e ottenere risposte miste. Quindi è davvero possibile fare questa roba senza che si complichi? Questo problema può risolvere il problema separando l'interfaccia utente tra Parent e Child, ma non voglio perché l'unione di entrambi gli elementi Child e Parent in un menu è piuttosto comune nello sviluppo di applicazioni aziendali e più user-friendly.

UPDATE: Sto provando la soluzione in basso ma non funziona.

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

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

    context.SaveChanges(); 
} 

Invece di rilevare i cambiamenti all'interno dei bambini, EF non sarà in grado di dire che cosa fare con il vecchio bambino. Ad esempio se parentFromDB ha 3 figli la prima volta che lo estraggo dal DB, allora cancello il 2 ° e il 3 ° figlio. Quindi sto ricevendo The relationship could not be changed because one or more of the foreign-key properties is non-nullable quando salvo.

Credo che questo sia ciò che è accaduto: The relationship could not be changed because one or more of the foreign-key properties is non-nullable

che mi ha portato di nuovo al punto di partenza perché nel mio scenario, non posso prendere dal DB e aggiornare la voce e chiamare SaveChanges.

+0

Non so se mi hanno frainteso la tua domanda. Disabilitare il rilevamento delle modifiche è un prerequisito? Perché altrimenti questo si ottiene facilmente con il cambio di tracciamento EF. Con il rilevamento delle modifiche non è necessario impostare in modo esplicito lo stato delle entità, EF lo farà per te, quindi tutte le modifiche apportate alla raccolta Childs, incluse le modifiche sulle entità nella raccolta, verranno automaticamente incluse nel set di modifiche quando si commette il contesto (SaveChanges). –

+0

Sono d'accordo con il commento di odyss-jii, ma voglio aggiungere ulteriori informazioni ad esso: ovviamente questo funziona solo quando si mantiene il contesto aperto tra il recupero e la modifica delle proprietà. altrimenti dovrai impostare tu stesso lo stato della tua entità e per questo dovrai prima ottenere tutto nel contesto, quindi dovrai creare una voce per ogni oggetto e impostarne lo stato. – DevilSuichiro

+0

puoi essere più specifico? Inserisci un codice, se possibile. Questo è ciò che intendo (non riesco a trovare la versione EF6). http://www.entityframeworktutorial.net/EntityFramework4.3/update-one-to-many-entity-using-dbcontext.aspx – warheat1990

risposta

1

I suoi amici lo fanno stranamente.

Questo richiede il caricamento pigro per ottenere Childs (ovviamente modificare per il vostro uso)

// ottenere genitore

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

ha scritto un metodo di prova tutta per voi. (Applicare al vostro caso)

EmailMessage (genitore) è il genitore e non ne ha o di molti EmailAttachment (figlio di)

[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(); 


     } 
    } 

Aggiornamento

Fate la vostra AutoMappper roba ... come hai detto nel tuo commento.

Quindi, quando sei pronto per salvare e lo hai indietro come i tipi corretti vale a dire una volta che rappresentano entità (Db), quindi fare questo.

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

Nel mio caso, otterrei l'esistente 'EmailMessage' insieme alle sue proprietà aka' EmailAttachment' dal contesto e uso 'AutoMapper' per mapparlo nel mio' EmailMessageViewModel' (questo ViewModel ha proprietà 1: 1 con 'EmailMessage'). Successivamente, eseguo il mapping di 'EmailMessageViewModel' in un nuovo oggetto' EmailMessage'. Questo nuovo 'EmailMessage' potrebbe avere un 'EmailAttachment' modificato/nuovo/rimosso'. Questo è quando non ho idea di come aggiornarlo nel contesto. Il mio post originale: http://stackoverflow.com/questions/32964990/confuse-about-tracking-in-ef-updating-entity-with-child-collection – warheat1990

+0

Facile, recuperare di nuovo l'originale ... e utilizzare l'automapper per cambiare la proprietà dell'entità (genitore) che è vista come allegata al contesto. Quindi imposta i Childs dell'entità recuperata ai nuovi childs. per esempio. Parent.Childs = Childs modificati. EF è abbastanza intelligente per risolvere tutto per te ... ed è per questo che EF è fantastico. – Seabizkit

+0

Ok, ma per quanto riguarda l'allegato appena aggiunto o l'allegato rimosso? Finora lo prendo come: 1. Scarica l'originale 2. Confrontalo con 'EmailMessageViewModel' 3. Imposta il' EntityState' su 'Modificato' se lo stesso ID trovato,' Aggiunto' se non trovato, e 'Eliminato 'se trovato nella versione scaricata ma non in' EmailMessageViewModel' Funzionerà? – warheat1990

7

perché EF non è in grado di rilevare i cambiamenti all'interno Esplorazione della struttura

Questo sembra essere una descrizione un po 'distorta del fatto che _dbContext.Entry(obj).State = EntityState.Modified non segna proprietà NAVIGAZIONE come modificata.

Ovviamente, EF tiene traccia delle modifiche nelle proprietà di navigazione. Tiene traccia delle modifiche nelle proprietà e nelle associazioni di tutte le entità collegate a un contesto. Pertanto, la risposta alla tua domanda, ora ha dichiarato positivamente ...

E 'possibile aggiornare collezione bambino in EF fuori dalla scatola

... è: sì .

L'unica cosa è: tu non lo fai fuori dalla scatola.

Il "out of the box" modo per aggiornare qualsiasi entità, sia che si tratti di un genitore o un bambino in qualche collezione è:

  • Fetch entità dal database.
  • Modificare le proprietà o aggiungere/rimuovere elementi alle proprie raccolte
  • Chiamare SaveChanges().

Questo è tutto. Ef tiene traccia delle modifiche e non si imposta mai l'entità State s in modo esplicito.

Tuttavia, in uno scenario disconnessi (n-tier), questo diventa più complicato. Eseguiamo la serializzazione e la deserializzazione delle entità, quindi non ci può essere alcun contesto che tenga traccia delle loro modifiche. Se vogliamo archiviare le entità nel database, ora è nostro compito far conoscere a EF le modifiche. Ci sono fondamentalmente due modi per farlo:

  • Imposta gli stati manualmente, in base a ciò che sappiamo circa le entità (come: una chiave primaria> 0 significa che essi esistono e devono essere aggiornati)
  • della vernice lo stato: recupera le entità dal database e riapplica le modifiche dalle entità deserializzate a esse.

Quando si tratta di associazioni, dobbiamo sempre dipingere lo stato. Dobbiamo ottenere le entità attuali dal database e determinare quali bambini sono stati aggiunti/cancellati. Non c'è modo di inferire questo dal grafico dell'oggetto deserializzato stesso.

Ci sono vari modi per alleviare questo noioso ed elaborato compito di dipingere lo stato, ma questo va oltre lo scopo di questo Q & A.Alcuni riferimenti: