2009-01-15 13 views
20

Ho 2 tabelle del database correlati, che nel look forma semplificata come questoLINQ to SQL: esecuzione degli ordini al momento della chiamata SubmitChanges()

Product(
    product_id, 
    name 
) 

ProductSpecs(
    spec_id, 
    product_id, 
    name, 
    value 
) 

chiave esterna viene impostato tramite campo product_id e tavolo ProductSpecs ha un vincolo univoco (product_id, name) pair.

Ora nella mia applicazione ASP.NET MVC quando l'utente modifica le specifiche del prodotto e salva i dati, elimino le vecchie specifiche e le inserisco tutte come nuove.

Faccio questo richiamando prima DataContext.DeleteAllOnSubmit() e fornendo prodotti (vecchi) attuali come parametro, quindi aggiungo nuove specifiche alla raccolta Product.ProductSpecs.

Quindi chiamo DataContext.SubmitChanges() e ricevo un errore che il mio vincolo univoco è stato violato.

Osservando le istruzioni SQL restituite da DataContenxt.GetChangeText(), posso vedere che gli INSERT vengono eseguiti prima dei DELETE (anche se ho chiamato DeleteAllOnSubmit() prima di Aggiungi).

Qual è la ragione di questo comportamento e come risolverlo o aggirarlo?

Grazie.

+0

Hai provato a chiamare SubmitChanges() dopo aver chiamato DeleteAllOnSubmit, ma prima di aggiungere i nuovi record? – RobS

+0

Questo eliminerebbe i record prima che io sappia per certo che i nuovi record sarebbero stati inseriti che è sbagliato. O esiste un modo per eseguire il rollback anche dopo la chiamata a SubmitChanges()? –

+0

È possibile includere le chiamate in entrata in una transazione e nel rollback se non si trovano nuovi record da inserire. Sebbene tu possa controllare se hai nuovi record prima di fare la cancellazione. – RobS

risposta

18

Sì, per qualche ragione, LINQ to SQL eseguire tutte le eliminazioni come ultima cosa e non c'è modo di cambiarlo.

O forse c'è. Non ho esaminato il DataContext codegen per vedere se possiamo ignorare qualcosa lì.

È possibile chiamare SubmitChanges() tutte le volte che si desidera. La mia soluzione alternativa, quando ho bisogno di eliminare come prima cosa, è contrassegnare le mie eliminazioni, chiamare SubmitChanges(), quindi eseguire inserti e aggiornamenti e chiamare nuovamente SubmitChanges.

Puoi avvolgere tutto all'interno di un TransactionScope:

var deletables = 
     from toDelete in db.NamedValues 
     where toDelete.Name == knownValue.Name 
     select toDelete; 

    using (var scope = new TransactionScope()) 
    { 
     db.NamedValues.DeleteAllOnSubmit(deletables); 
     db.SubmitChanges(); 

     db.NamedValues.InsertOnSubmit(knownValue); 
     db.SubmitChanges(); 

     scope.Complete(); 
    } 
3

Ho avuto questo stesso problema. Mi si avvicinò con un trucco per lavorare intorno ad esso, che sembra funzionare per me: ho eseguito manualmente alcuni SQL per ciascun potenzialmente problematici eliminare nel cambiamento set prima di chiamare SubmitChanges():

foreach (object o in m_Db.GetChangeSet().Deletes) 
{ 
    LibraryMember m = o as LibraryMember; 

    if (m != null) 
    { 
     m_Db.ExecuteCommand("DELETE FROM LibraryMembers WHERE LibraryMemberId={0}", m.LibraryMemberId); 
    } 
} 

m_Db.SubmitChanges() 
+0

Perché i downvotes tutto ad un tratto? 2 anni dopo ho postato questa risposta! – Grokys

+2

Probabilmente hai downvoted per SQL dinamico sciatto. Ho aumentato il tuo punteggio di 1, ma si dovrebbe notare di non utilizzare mai la concatenazione di stringhe con il testo SQL per timore che diventi un'abitudine pericolosa. – DenNukem

+1

@DenNukem: vero! Modificato per usare i parametri, grazie! – Grokys

1

Un'altra soluzione è quella di collegare le modifiche a un altro contesto di dati nell'ordine corretto. Tuttavia il lato negativo di questa soluzione è che tutte le entità ancora referenziate dal resto dell'applicazione che provenivano dal contesto originale non saranno più valide :(

Ho provato una leggera variazione su questo, creando un secondo dato temporaneo contesto per il commit. Ma io non riesco a capire come poi ottenere il contesto dati originali in uno stato pulito.

E 'molto frustrante :(

public void Save() 
{ 
    string connectionString = m_Db.Connection.ConnectionString; 
    IList<object> deletes = m_Db.GetChangeSet().Deletes; 
    IList<object> inserts = m_Db.GetChangeSet().Inserts; 
    IList<object> updates = m_Db.GetChangeSet().Updates; 

    m_Db.Dispose(); 
    m_Db = new MyDataContext(connectionString); 

    Attach(m_Db, deletes); 
    m_Db.SubmitChanges(); 
    Attach(m_Db, updates); 
    m_Db.SubmitChanges(); 
    Attach(m_Db, inserts); 
    m_Db.SubmitChanges(); 
} 

void Attach(DataContext context, IList<object> items) 
{ 
    foreach (object o in items) 
    { 
     context.GetTable(o.GetType()).Attach(Clone(o)); 
    } 
} 

object Clone(object o) 
{ 
    object result = o.GetType().GetConstructor(Type.EmptyTypes).Invoke(null); 
    foreach (PropertyInfo property in o.GetType().GetProperties()) 
    { 
     if (property.PropertyType.IsValueType) 
     { 
      property.SetValue(result, property.GetValue(o, null), null); 
     } 
    } 
    return result; 
} 
0

Invece di buttare via tutti i bambini e li ricrea, prendere in considerazione un po 'più di sforzo e farlo in due fasi: Aggiungere gli elementi che non sono già stati aggiunti e rimuovere quelli che sono stati aggiunti in precedenza, ma non più desiderati. Ad esempio, nel sito ThinqLinq.com, ho i post che possono essere assegnati alle categorie. Nella schermata di modifica, fornisco un elenco di categorie che possono essere selezionate e deselezionate.Quando ricevo il messaggio che contiene l'elenco delle categorie selezionate, io uso il seguente per eliminare e aggiornare i record appropriati:

Dim selectedCats() As String = CType(ValueProvider("categories").RawValue, String()) 
For Each catId In selectedCats.Except(From cp In currentPost.CategoryPosts _ 
             Select id = cp.CategoryID.ToString()) 
    'Add new categories 
    currentPost.CategoryPosts.Add(New CategoryPost With {.CategoryID = CInt(catId)}) 
Next 

'Delete removed categories 
dc.CategoryPosts.DeleteAllOnSubmit(From cp In currentPost.CategoryPosts _ 
            Where Not selectedCats.Contains(cp.CategoryID.ToString)) 
3

Una soluzione migliore che non richiede sapere in anticipo quali entità sono cambiate è quello di utilizzare il DataContext di metodi parziali per intercettare gli inserti e premere prima le eliminazioni.

public partial class YourDataContext 
{ 
    List<EntityX> _deletedEntities = new List<EntityX>(); 

    partial void InsertEntityX(EntityX instance) 
    { 
     // Run deletes before inserts so that we don't run into any index violations 
     var deletes = 
      this.GetChangeSet().Deletes 
      .Where(d => d.GetType().Equals(instance.GetType())) 
      .Cast<EntityX>().ToList(); 

     var replaced = deletes.SingleOrDefault(d => d.UniqueKeyId == instance.UniqueKeyId); 

     if (replaced != null) 
     { 
      DeleteEntityX(replaced); 
      _deletedEntities.Add(replaced); 
     } 

     this.ExecuteDynamicInsert(instance); 
    } 

    partial void DeleteEntityX(EntityX instance) 
    { 
     if (_deletedEntities.Contains(instance)) 
     { 
      _deletedEntities.Remove(instance); 
      return; 
     } 

     this.ExecuteDynamicDelete(instance); 
    } 
} 
+0

Questa è di gran lunga la soluzione più elegante. Il contesto dati generato dall'ORM crea persino questi stub di metodo parziale per te; questo suggerisce che questa soluzione è probabilmente quella che i creatori avevano in mente. Questo richiede più voti positivi. – Eric

Problemi correlati