2010-04-21 15 views
8

Quindi ho un veramente tempo difficile capire quando dovrei collegarmi a un oggetto e quando non dovrei collegarmi a un oggetto. Per prima cosa, ecco un piccolo diagramma del mio modello di oggetto (molto semplificato).LINQ to SQL: per allegare o non allegare

Schema

Nel mio DAL creo un nuovo DataContext ogni volta che faccio un'operazione di dati relativi. Supponiamo, ad esempio, di voler salvare un nuovo utente. Nel mio livello aziendale creo un nuovo utente.

var user = new User(); 
user.FirstName = "Bob"; 
user.LastName = "Smith"; 
user.Username = "bob.smith"; 
user.Password = StringUtilities.EncodePassword("MyPassword123"); 
user.Organization = someOrganization; // Assume that someOrganization was loaded and it's data context has been garbage collected. 

Ora voglio andare Salva questo utente.

var userRepository = new RepositoryFactory.GetRepository<UserRepository>(); 
userRepository.Save(user); 

Neato! Ecco la mia logica di salvataggio:

public void Save(User user) 
{ 
if (!DataContext.Users.Contains(user)) 
{ 
    user.Id = Guid.NewGuid(); 
    user.CreatedDate = DateTime.Now; 
    user.Disabled = false; 

    //DataContext.Organizations.Attach(user.Organization); 
    DataContext.Users.InsertOnSubmit(user); 
} 
else 
{ 
    DataContext.Users.Attach(user); 
} 

DataContext.SubmitChanges(); 

// Finished here as well. 
user.Detach(); 
} 

Quindi, eccoci. Noterai che commento il bit in cui DataContext si collega all'organizzazione. Se io attribuisco alla organizzazione ottengo la seguente eccezione:

NotSupportedException:Un tentativo è stato fatto per Allega o Aggiungi un ente che non è nuovo, forse avendo stato caricato da un altro DataContext. Questo non è supportato.

Hmm, non funziona. Permettetemi di provarlo con lo senza il collegamento (ad esempio commentare quella riga sull'attribuzione all'organizzazione).

DuplicateKeyException:Non puoi aggiungere un soggetto con una chiave che è già in uso .

WHAAAAT? Posso solo supporre che stia cercando di inserire una nuova organizzazione che è ovviamente falsa.

Quindi, qual è l'affare ragazzi? Cosa dovrei fare? Qual è l'approccio corretto? Sembra che L2S rende questo un bel po 'più difficile di quanto dovrebbe essere ...

EDIT: Ho appena notato che se provo a guardare il set di modifica in attesa (dataContext.GetChangeSet()) ottengo lo stesso NotSupportedException Ho descritto prima !! Che diavolo, L2S ?!

+0

Solo curioso, perché non stai utilizzando chiavi esterne nel database per indicare la relazione nel tuo modello a oggetti? –

+0

Hmm, io sono, sono nel DB ... non sono sicuro del motivo per cui non sono entrati nel diagramma (cioè mi aspettavo una chiave grigia), ma avevo l'impressione che la piccola freccia passasse da una scatola a l'altro indicava che era presente una chiave straniera. –

+0

Stai usando L2S non come dovrebbe essere usato. Normalmente, Attach non viene quasi mai chiamato. Perché stai usando un repo in primo luogo? Sembra solo limitare ciò che puoi fare e non offrirti nulla in cambio. Con L2S, DataContext * è * il repository. – usr

risposta

1

Quindi "attach" viene utilizzato quando si prende un oggetto che esiste dal database, lo si scollega (ad esempio lo si esegue marshalling su un servizio Web da qualche altra parte) e si desidera reinserirlo nel database. Invece di chiamare .Attach(), chiama invece .InsertOnSubmit(). Ci sei quasi concettualmente, stai solo usando il metodo sbagliato per fare quello che vuoi.

+0

Grazie per la risposta ... che dire in questo genitore -> Relazione bambino che sto andando avanti? Se sto inserendo un nuovo oggetto figlio (cioè l'utente nel diagramma sopra), devo prima Allegare all'oggetto padre? –

+1

Lasciami dire così. Ho scritto circa 5 app linq in sql ora e non ho mai dovuto usare il metodo Attach(). In realtà non è necessario nemmeno assegnare la relazione tra i due oggetti se non si desidera fino a quando gli ID nei campi correlati sono d'accordo. L2SQL capirà cosa intendi quando chiami SubmitChanges(). –

+0

Sì. Così ha inserito una nuova riga con un nuovo ID nel DB. Non sei sicuro di come hai ottenuto l'aggiornamento delle righe esistenti usando l'ID? Ho ottenuto l'oggetto da DBCOntext, ho modificato alcuni campi, ho inviato l'oggetto indietro per essere salvato. Niether Attach o InsertOnSubmit() funziona, a meno che non elimini la vecchia riga? – ppumkin

6

Potrebbe non funzionare esattamente come questo sotto il cofano, ma ecco come concettualizzarlo: quando invochi un oggetto da un DataContext, una delle cose che Linq fa è tracciare le modifiche a questo oggetto nel tempo in modo che sappia cosa salvare se si inviano modifiche.Se si perde questo contesto dati originale, l'oggetto Linq da esso richiamato non ha la cronologia di ciò che è stato modificato in esso dal momento in cui è stato richiamato dal DB.

Ad esempio:

DbDataContext db = new DbDataContext(); 
User u = db.Users.Single(u => u.Id == HARD_CODED_GUID); 
u.FirstName = "Foo"; 
db.SubmitChanges(); 

In questo caso, poiché l'oggetto utente è stato convocato dal contesto dei dati, è stato rintracciando tutte le modifiche "u" e sa come presentare le modifiche al DB.

Nel tuo esempio, hai un oggetto Utente che è stato persistuto da qualche parte (o passato da altrove e non ha il suo DataContext originale da cui è stato chiamato). In questo caso, è necessario allegarlo al nuovo contesto di dati.

User u; // User object passed in from somewhere else 
DbDataContext db = new DbDataContext(); 
u.FirstName = "Foo"; 
DbDataContext.Users.Attach(u); 
db.SubmitChanges(); 

Dal momento che il rapporto tra l'utente e l'organizzazione è solo un GUID (organizationid) nel modello dei dati, si hanno solo per fissare l'oggetto utente.

non sono sicuro circa il vostro codice di impalcature, ma forse qualcosa di simile:

private const Guid DEFAULT_ORG = new Guid("3cbb9255-1083-4fc4-8449-27975cb478a5"); 
    public void Save(User user) 
    { 
     if (!DataContext.Users.Contains(user)) 
     { 
      user.Id = Guid.NewGuid(); 
      user.CreatedDate = DateTime.Now; 
      user.Disabled = false; 
      user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just 
               // via a GUID, not by assigning an 
               // Organization object 

      DataContext.Users.InsertOnSubmit(user); 
     } 
     else 
     { 
      DataContext.Users.Attach(user); 
     } 

     DataContext.SubmitChanges(); 

    } 
+0

È necessario utilizzare ** overload ** 'Attach (user, true)' Per dire a Linq2SQL che il modello è cambiato rispetto all'originale. Il terzo sovraccarico consente di passare l'originale e il modificato e lo rimappare in background. Eccezionale. Ancora non capisco come Dave Markle abbia usato 'InsertOnSubmit' per aggiornare? – ppumkin

0

ho usato un grande tavolo con oltre 400 colonne. In nessun modo ho intenzione di mappare e testare tutto questo!

Ottenere l'oggetto originale dal database e collegarlo con l'oggetto modificato. Assicurati che l'oggetto che rientra sia completamente popolato altrimenti lo sostituirà con gli spazi vuoti!

Oppure è possibile copiare l'originale GET in memoria e lavorare su una copia corretta (non solo di riferimento) del MOdel, quindi passare l'originale e quello modificato in, invece di ottenere come faccio nell'esempio. Questo è solo un esempio di come funziona.

public void Save(User user) 
{ 

    if (!DataContext.Users.Contains(user)) 
    { 
     user.Id = Guid.NewGuid(); 
     user.CreatedDate = DateTime.Now; 
     user.Disabled = false; 
     user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just 
              // via a GUID, not by assigning an 
              // Organization object 

     DataContext.Users.InsertOnSubmit(user); 
    } 
    else 
    { 
     var UserDB = DataContext.Users.FirstOrDefault(db => db.id == user.id); //Costs an extra call but its worth it if oyu have 400 columns! 
     DataContext.Users.Attach(user, userDB); //Just maps all the changes on the flu 
    } 

    DataContext.SubmitChanges(); 

}