2013-12-17 19 views
5

ho le seguenti classi:Salvataggio relazione molti a molti - Entity Framework

public class item 
    { 
     public int NodeID 
     { 
      get ; 
      set ; 
     } 
     public int Weight 
     { 
      get ; 
      set ; 
     } 
     public int Category 
     { 
      get ; 
      set ; 
     } 
    } 
    public class Recipients 
    { 
     public int RecipientID 
     { 
      get ; set; 
     } 
    } 

    public class Nodes 
    { 
     public List<int> RecipientList 
     { 
      get ; 
      set ; 
     } 
     public item Item 
     { 
      get ; set; 
     } 
     public int Capacity 
     { 
      get ; set; 
     } 
     public int NodeID 
     { 
      get ; set; 
     } 
     public int Weight 
     { 
      get ; set; 
     } 
     public int Category 
     { 
      get ; set; 
     } 
    } 

E ho cercato di salvarla nel mio DB esistente che ha le seguenti tabelle:

1) Category 
2) Items 
3) Nodes (Node and Items has 1-1 relationship) 
4) Recipients 
5) NodeRecipients (This table show the many to many relationship between Nodes and Recipients) 

Io uso VS2012 per creare un modello EF come illustrato dallo schema seguente (notare che Nodi deriva da Articoli nell'EF)

enter image description here

Ho un metodo che cerca di salvare i nodi ed i suoi destinatari

public void SaveNodeAndRecipient(List<Nodes> MyNodes) 
    { 
     using (var db = new MyEntities()) 
     { 
      foreach (var n in MyNodes) 
      { 
       Node n1 = new Node() { NodeID = n.NodeID, categoryID = n.Category, Capacity = n.Capacity }; 
       db.Items.Add(n1); 
       foreach (var r in n.RecipientList) 
       { 
        Recipient rep; 
        if (!db.Recipients.Select(x => x.recipientID).Contains(r)) 
        { 
         rep = new Recipient() { recipientID = r }; 
         db.Recipients.Add(rep); 
        } 
        else 
        { 
         rep = db.Recipients.Where(x => x.recipientID == r).FirstOrDefault(); 
        } 
        Node_Recipient nr = new Node_Recipient(){RecipientID=r,NodeID=n.NodeID}; 
        n1.Node_Recipient.Add(nr); 
       } 
      } 
      db.SaveChanges(); 
     } 
    } 

MyEntities è il modello EF ed è stato dichiarato nel appconfig:

<connectionStrings> 
    <add name="MyEntities" connectionString="xxxxx" providerName="System.Data.EntityClient" /> 
    </connectionStrings> 

Tutto è andato bene e compilato senza alcun problema fino a quando non ho provato a fare il savechanges. Ho ottenuto questo errore (non molto descrittivo)

enter image description here

Chiunque sa che cosa sta succedendo? Ho l'impressione che il rapporto tra molti e molti sia il colpevole, ma non è in grado di individuare ciò che lo sta causando. Per favore aiuto!

+1

Hit 'Visualizza dettagli ...' per vedere l'eccezione interna. – nekno

+1

Rilascia il DB e riprova. A meno che non si inseriscano le Migrazioni o si lasci sempre il DB, ogni volta che si cambiano i modelli si otterrà l'errore DbUpdate – Tico

+0

@nekno: Eccezione interna verificata e ottenuto questo: l'istruzione INSERT è in conflitto con il vincolo FOREIGN KEY "FK_Node-Recipient_Recipient". Il conflitto si è verificato nel database "MyEntities", nella tabella "dbo.Recipient", nella colonna "recipientID". La dichiarazione è stata chiusa. – user1205746

risposta

3

il problema è che si sta impostando Node_Recipient.recipientID valori, ma dalla tua descrizione sono abbastanza sicuro che questi valori di chiave primaria vengono generati dal database. Pertanto non è garantito che i valori (r) siano presenti quando gli oggetti vengono salvati.

Peggio ancora - non è nemmeno probabile che il nuovo mantenga i valori assegnati, quindi è possibile creare false associazioni.

Ecco cosa dovrebbe funzionare per voi. Vedi alcuni commenti qui sotto.

foreach (var n in MyNodes) 
{ 
    Node n1 = new Node { 
          NodeID = n.NodeID, 
          categoryID = n.Category, 
          Capacity = n.Capacity 
         }; 
    foreach (var r in n.RecipientList) 
    { 
     Recipient rep = db.Recipients.Find(r); 
     if (rep == null) 
     { 
      rep = new Recipient(); // see comment 1. 
     } 
     Node_Recipient nr = new Node_Recipient { 
               Recipient = rep, 
               Node = n1 
               // See comment 2 
               }; 
     n1.Node_Recipient.Add(nr); 
    } 
    db.Items.Add(n1); // see comment 3 
} 
  1. No recipientId è impostato qui.

  2. Qui si impostano i riferimenti invece dei valori ID. EF assegna i valori della chiave esterna corretta "just in time" mentre salva le modifiche.

  3. Questo segna tutti gli oggetti nel grafico oggetto sotto il nuovo Node come Added, a meno che non siano già conoscono al contesto, che è vero per il Recipients hai incontrato per db.Recipients.Find(r).

Quanto molti-a-molti, la tabella Node_Recipient sembra una tabella di collegamento pura, cioè un tavolo con soltanto due chiavi esterne.Ci deve essere stato un motivo per cui EF non ha generato un modello con un'associazione trasparente: n, senza un'entità Node_Recipient. Normalmente lo farebbe. Quando hai generato il modello, Node_Recipient contiene altre colonne che hai rimosso in seguito?

Se si desidera questa associazione m: n, è possibile provare a rigenerare il modello. Ciò dovrebbe produrre una classe Nodes con una raccolta Recipients e una Recipient con una raccolta Nodes. Impostare le associazioni dovrebbe quindi riguardare l'aggiunta di nuovi destinatari a Node.Recipients.

+0

Mi chiedevo perché EF non abbia generato un modello con un'associazione trasparente: n. No, Node_Recipient è sempre stato così, nessuna colonna rimossa. Una cosa che ho notato e che potrebbe avere o meno impatto su questo problema, è stata l'associazione tra Item e Node. Le loro chiavi primarie sono esatte lo stesso chiamato NodeID e dovrebbero essere 1-1. Ecco perché ho fatto ereditare il nodo dall'elemento. Ma prima di rendere il tipo di base del nodo su Item, ho notato che la loro associazione era 1: 0 (non 1: 1) Come impongo l'associazione 1: 1 in modo che un'eccezione possa essere generata se l'associazione 1: 1 viene violata? – user1205746

+0

Dovresti essere in grado di gestirlo con i vincoli di molteplicità dell'associazione in edmx. Ma nel modello di dati la chiave primaria di 'Item' dovrebbe * anche * essere una chiave estranea a' Node' per renderla veramente difficile. Ma forse dovresti fare un'altra domanda con i dettagli su questa associazione specifica (modello di classe e modello di dati). –

+0

Per quanto riguarda l'associazione m: n, non mi preoccuperei troppo di essere ora una classe visibile. Queste tabelle di giunzione hanno un modo di evolversi in entità aziendali perché prima o poi gli utenti registreranno i dati * relativi * all'associazione. Con una classe visibile sei preparato meglio per tali mutevoli requisiti. –

0

Basta guardare il codice, senza la particolare eccezione, mi chiedo se le modifiche a n1 devono essere fatte prima di aggiungere al database:

Node n1 = new Node() { NodeID = n.NodeID, categoryID = n.Category, Capacity = n.Capacity }; 

foreach (var r in n.RecipientList) 
{ 
    if (db.Recipients.Where(x => x.recipientID == r).FirstOrDefault() == null) 
    { 
     Recipient rep = new Recipient() { recipientID = r }; 
     db.Recipients.Add(rep); 
    } 

    Node_Recipient nr = new Node_Recipient(){RecipientID=r,NodeID=n.NodeID}; 
    n1.Node_Recipient.Add(nr); 
} 

db.Items.Add(n1); // add here, after modifications to n1 
+0

Poiché Node_Recipient è fondamentalmente una relazione molti-a-molti tra Nodo e Destinatario, sono sorpreso di dover aggiungere letteralmente i record Node_Recipient. Credo che EF sia abbastanza intelligente da creare quel record quando aggiungo Node e poi Recipient in Node. Forse il mio EF non è stato impostato correttamente? Sono molto nuovo in questo fantastico concetto quindi, potrei sbagliare. – user1205746

+0

Non è necessario caricare le informazioni dal database, l'approccio di Gert Arnold è corretto –

+1

Sono d'accordo, l'aspetto di @ Gert è corretto. Stavo facendo un tentativo al buio senza le informazioni sull'errore. Si noti, tuttavia, che 'db.Recipients.Find (r)' eseguirà una query sul database se l'oggetto non è presente nel contesto. Se c'è un 'cache miss', quindi chiamare 'Find()' è un modo più pulito, semanticamente equivalente, di chiamare 'db.Recipients.Where (x => x.recipientID == r) .FirstOrDefault()' (no eccezione per le chiavi duplicate) o 'SingleOrDefault()' (con l'eccezione per le chiavi duplicate). Passare il lambda expr a entrambi i metodi è anche un'opzione invece di usare 'Dove()'. – nekno

Problemi correlati