2010-05-07 8 views
15

Ho una relazione molti-a-molti tra Issues e Scopes nel mio contesto EF. In ASP.NET MVC, viene visualizzato un modulo Modifica che consente all'utente di modificare un determinato problema. Nella parte inferiore del modulo, è presente un elenco di caselle di controllo che consentono loro di selezionare quali ambiti si applicano a questo problema. Quando si modifica un problema, probabilmente avrà sempre alcuni ambiti associati - le caselle saranno già controllate. Tuttavia, l'utente ha la possibilità di controllare più ambiti o rimuovere alcuni degli ambiti correntemente controllati. Il mio codice sembrava qualcosa di simile per salvare solo il problema:Entity Framework Update Entità insieme alle entità figlio (aggiungere/aggiornare se necessario)

  using (var edmx = new MayflyEntities()) 
      { 
       Issue issue = new Issue { IssueID = id, TSColumn = formIssue.TSColumn }; 
       edmx.Issues.Attach(issue); 

       UpdateModel(issue); 

       if (ModelState.IsValid) 
       { 
        //if (edmx.SaveChanges() != 1) throw new Exception("Unknown error. Please try again."); 
        edmx.SaveChanges(); 
        TempData["message"] = string.Format("Issue #{0} successfully modified.", id); 
       } 
      } 

Così, quando provo ad aggiungere nella logica di salvare l'scopi associato, ho provato diverse cose, ma alla fine, questo è ciò che ha reso il più senso per me:

  using (var edmx = new MayflyEntities()) 
      { 
       Issue issue = new Issue { IssueID = id, TSColumn = formIssue.TSColumn }; 
       edmx.Issues.Attach(issue); 

       UpdateModel(issue); 

       foreach (int scopeID in formIssue.ScopeIDs) 
       { 
        var thisScope = new Scope { ID = scopeID }; 
        edmx.Scopes.Attach(thisScope); 
        thisScope.ProjectID = formIssue.ProjectID; 
        if (issue.Scopes.Contains(thisScope)) 
        { 
         issue.Scopes.Attach(thisScope); //the scope already exists 
        } 
        else 
        { 
         issue.Scopes.Add(thisScope); // the scope needs to be added 
        } 
       } 

       if (ModelState.IsValid) 
       { 
        //if (edmx.SaveChanges() != 1) throw new Exception("Unknown error. Please try again."); 
        edmx.SaveChanges(); 
        TempData["message"] = string.Format("Issue #{0} successfully modified.", id); 
       } 
      } 

Ma, purtroppo, che getta solo la seguente eccezione:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 

che cosa sto facendo di sbagliato?

risposta

12

Gli stub sono generalmente efficaci solo per le relazioni 1-*. Le relazioni *-* introducono un diverso insieme di sfide.

Vale a dire che quando si collegano entrambe le estremità - a differenza di 1-* - non si ha ancora idea se la relazione esiste già o meno.

Quindi questo significa che questo codice:

if (issue.Scopes.Contains(thisScope)) 

è destinata probabilmente ad return false ogni volta.

Quello che vorrei fare è questo:

edmx.Issues.Attach(issue); 
UpdateModel(issue); 
// or ctx.LoadProperty(issue, "Scopes") if it is a POCO class. 
issue.Scopes.Load(); // hit the database to load the current state. 

Ora è necessario scoprire che cosa è necessario aggiungere & rimuovere dal problema.Scopes. È possibile farlo confrontando in base all'ID.

vale a dire se si dispone di un set di ID Ambito si desidera avere legato alla questione (relatedScopes)

Allora questo codice funziona fuori cosa aggiungere e cosa togliere.

int[] toAdd = relatedScopes.Except(issue.Scopes.Select(s => s.ID)).ToArray(); 
int[] toRemove = issue.Scopes.Select(s => s.ID).Except(relatedScopes).ToArray(); 

Ora per toadd si esegue questa operazione:

foreach(int id in toAdd) 
{ 
    var scope = new Scope{Id = id}; 
    edmx.Scopes.Attach(scope); 
    issue.Scopes.Add(scope); 
} 

E per ciascun ambito è necessario rimuovere

foreach(int id in toRemove) 
{ 
    issue.Scopes.Remove(issue.Scopes.Single(s => s.ID == id)); 
} 

Ormai dovrebbero essere formati i rapporti corretti.

Spero che questo aiuti

Alex

Microsoft

+1

Perfetto! E guardando SQL profiler, è solo una chiamata "extra" al DB per .Load(), ma molto più pulito di aggiungere/rimuovere rispetto al modo in cui lo usavo a mano con le stored procedure. Grazie! – Jorin

+0

James bella risposta. Questo è un problema comune in molte applicazioni (aggiungere/rimuovere i tag in modo efficace). Non è ora che una funzione a livello di sistema esegua questa operazione senza uno schermo pieno di codice? Qualcosa come * issue.Scopes.ReplaceWith (myScopes); * – TFD

+0

@TFD sì, ti sento. In caso contrario, la squadra EF ha molto da offrire, un semplice metodo di estensione potrebbe fare il trucco anche se giusto? –

0

Attenzione, questo è solo dalla parte superiore della mia testa, non l'ho provato.

Non penso che sia possibile impostare una chiave esterna proprio come si fa con ProjectID.

è necessario recuperare il progetto e aggiungerlo alla proprietà di navigazione scope.Project. EF si prenderà cura delle relazioni quando la inserirai.

Ancora una volta, non ho provato e potrebbe essere sbagliato, ma vale la pena provare. Forse questo ti aiuta a metterti sulla strada ..

+0

no, non la penso così. Ho appena cambiato la riga 'thisScope.ProjectID = formIssue.ProjectID;' per 'thisScope.Project = issue.Project;' e ha ancora lo stesso errore. è questo che intendevi? – Jorin

+0

Ok, proverò da solo e pubblicherò il codice che troverò se non risolverai il problema da solo. –

+0

no, mi sono stancato di provare diverse opzioni. Nel mio caso di test, ho 3 ambiti, 2 dei quali sono già associati a questo problema. Faccio in modo che tutti e 3 siano controllati sul mio modulo, quindi i 2 esistenti dovrebbero essere aggiornati e 1 dovrebbe essere associato al problema. Ottengo risultati variabili in base a quello che cerco, ma nulla che faccia ciò che è previsto. Stranamente, con il metodo '.Add', a volte cerca di aggiungere un nuovo scope al database invece di" aggiungere "una nuova associazione con questo problema, ma non riesco a capire come farlo funzionare correttamente . – Jorin

0

** Disclaimer: Sono relativamente nuovo a EF, prendo la mia risposta con un secchio di sale.

Se si tratta di un modulo di modifica, ritengo che l'oggetto del problema non debba essere un problema "nuovo", ma dovrebbe estrarre il problema dall'archivio dati. Per quanto posso vedere facendo:

Issue issue = new Issue { IssueID = id, TSColumn = formIssue.TSColumn }; edmx.Issues.Attach(issue); si sta effettivamente creando un nuovo problema con l'ID di quello che si sta tentando di modificare.

Ancora una volta, sono qui alla ricerca di modi per dare un senso a EF. A volte mi mancano le mie dichiarazioni SQL.

+0

Grazie, ma no. Come ho detto, la parte relativa al problema funziona bene, sono solo le entità figlio a cui non riesco ad essere aggiornato. il "nuovo" che vedi c'è solo un modo per creare un'entità stub piuttosto che usare EntityKey. Vedi questo articolo: http://blogs.msdn.com/alexj/archive/2009/06/19/tip-26-how-to-avoid-database-queries-using-stub-entities.aspx – Jorin

+1

Ooohhh pretty !! Dovrò provare questa cosa dell'entità stub, ridurrà molte query inutili. Grazie! –

Problemi correlati