e giunse il fatidico giorno della consegna, il cliente entusiasta apprezza la semplicità di utilizzo del software, a momenti mi lusinga sta cosa, anzi no...mi lusinga...in genere i clienti dicono cose del tipo "questo non mi piace" "questo nemmeno"...invece io ne ho beccato uno di quelli tranquilli...poi mi fa...bella la demo poi la versione completa me la installate sul server ??così possiamo accederci da tutte le postazioni?...ed io come in un flashback...vedo passarmi tutte le migliaia di righe di codice che ho scritto per portargli un'applicazione desktop con db in locale, e lui mi dice (cosa che nessuno dell'area sviluppo sapeva)che ne vuole una distribuita...ed il panico scese sui miei neuroniinizio a pensare di dover esporre un servizio sulla macchina server, poi ci penso e mi dico "va bè ma il servizio lo espone sql server express"io devo solo fare una stringa di connessione che punti al serverpoi ci penso e mi torna in mente una cosa importante "la concorrenza"e mi ritrovo qui a chiedervi (che cavolo ci hai messo mezz'ora per fare sta domanda!!)avendo utilizzato i dataset tipizzati con i table adapter in maniera del tutto trasparentecome faccio a gestire la concorrenza???
Purtroppo, come immagino tu sappia data la tua disperazione, le applicazini vanno pensate per essere utilizzate in concorrenza, è un requisito quello che ti hanno dato e la cosa migliore in certi casi ancora embrionali e rifare tutto...ma nessuno ne ha il coraggio. Quindi ecco un po' di consigli:
1. Sicuramete, visto che ti preoccupi della cosa, sai benissimo che i dataset tipizzati non supportano l'aggiornamento di più tabelle in modalità transazionale, questo perchè molto semplicemente il pattern table module ragiona sulle singole tabelle; la tua fortuna però è che i TableAdapter sono classi autogenerate che al loro interno usano ado.net, che come ben sai supporta pienamente le transazioni grazie alla classe Transaction: peccato che il table adpater non espongala connection da cui poter richiedere un oggetto di questo tipo. Le soluzioni possibili sono due:
1.a) i table adapter non hanno una classe base, quindi niente ereditarietà, però sono classi partial, quindi puoi rendere pubblica la connectione da quella generare l'oggetto transaction su cui invocare i classici metodi Commit e Roolback quando ti pare. Quindi, crea una connection, genera la transaction e setta la connection, mediante la property che hai creato nelle classi partial, a tutti i table adapter interessati (dato che la transaction è legata alla connection deve essere la stessa per le tabelle che vuoi gestire in modo transazionale)
Dim conn As New SqlClient.SqlConnection(connectionString) Call conn.Open() Dim transaction As SqlClient.SqlTransaction = conn.BeginTransaction() testata.Connection = conn dettaglio.Connection = conn ....
Dim
conn As New SqlClient.SqlConnection(connectionString)
Call conn.Open()
Dim transaction As SqlClient.SqlTransaction = conn.BeginTransaction()
testata.Connection = conn
dettaglio.Connection = conn
....
2.a) con un po' di sana reflection, a leggero scapito delle prestazioni, puoi semplicemente fare un metodo che si occupi della cosa
Public Shared Function avviaTransazione(ByRef tableAdapters() As Object, ByVal connectionString As String) As SqlClient.SqlTransaction Try 'Crea e apre la connessione e avvia la transazione Dim conn As New SqlClient.SqlConnection(connectionString) Call conn.Open() Dim transaction As SqlClient.SqlTransaction = conn.BeginTransaction() 'Per ogni Table Adapter... Dim tableAdapter As Object For Each tableAdapter In tableAdapters 'Recupera il tipo del table adapter e setta la connessione e la transazione Dim tableAdapterType As Type = tableAdapter.GetType() tableAdapterType.GetProperty( "Connection", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).SetValue(tableAdapter, conn, Nothing) Dim adapter As SqlClient.SqlDataAdapter = tableAdapterType.GetProperty("Adapter", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).GetValue(tableAdapter, Nothing) adapter.InsertCommand.Transaction = transaction adapter.UpdateCommand.Transaction = transaction adapter.DeleteCommand.Transaction = transaction 'setta la transazione delle command collection Call tableAdapterType.GetProperty("InitCommandCollection", Reflection.BindingFlags.InvokeMethod Or Reflection.BindingFlags.NonPublic) Dim commandCollection() As SqlClient.SqlCommand = tableAdapterType.GetProperty("CommandCollection", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).GetValue(tableAdapter, Nothing) Dim connCollection As SqlClient.SqlCommand For Each connCollection In commandCollection connCollection.Transaction = transaction Next Next 'Restituisce la transazione Return transaction Catch ex As Exception Call MsgBox(ex.Message, MsgBoxStyle.Critical, "FunzioniComuni->avviaTransazione()") Return Nothing End Try End Function
Public Shared Function avviaTransazione(ByRef tableAdapters() As Object, ByVal connectionString As String) As SqlClient.SqlTransaction
Try
'Crea e apre la connessione e avvia la transazione
Dim conn As New SqlClient.SqlConnection(connectionString)
'Per ogni Table Adapter...
Dim tableAdapter As Object
For Each tableAdapter In tableAdapters
'Recupera il tipo del table adapter e setta la connessione e la transazione
Dim tableAdapterType As Type = tableAdapter.GetType() tableAdapterType.GetProperty(
tableAdapterType.GetProperty(
"Connection", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).SetValue(tableAdapter, conn, Nothing)
Dim adapter As SqlClient.SqlDataAdapter = tableAdapterType.GetProperty("Adapter", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).GetValue(tableAdapter, Nothing) adapter.InsertCommand.Transaction = transaction adapter.UpdateCommand.Transaction = transaction adapter.DeleteCommand.Transaction = transaction
adapter.InsertCommand.Transaction = transaction
adapter.UpdateCommand.Transaction = transaction
adapter.DeleteCommand.Transaction = transaction
'setta la transazione delle command collection
Call tableAdapterType.GetProperty("InitCommandCollection", Reflection.BindingFlags.InvokeMethod Or Reflection.BindingFlags.NonPublic)
Dim commandCollection() As SqlClient.SqlCommand = tableAdapterType.GetProperty("CommandCollection", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).GetValue(tableAdapter, Nothing)
Dim connCollection As SqlClient.SqlCommand
For Each connCollection In commandCollection connCollection.Transaction = transaction
connCollection.Transaction = transaction
Next
'Restituisce la transazione
Return transaction
Catch ex As Exception
Call MsgBox(ex.Message, MsgBoxStyle.Critical, "FunzioniComuni->avviaTransazione()")
Return Nothing
End Try
End Function
2. Se hai usato campi identity come chiave in testata che usi come chiave esterna nel dettaglio...hai fatto una pessima scelta...questo perchè i table adapter basano l'identity sui dati correntemente caricati invece che sui dati presenti nel db al momento del salvataggio il che in concorrenza potrebbe creare la spiacevole situazione di accodare il tuo dettaglio a una testata creata in contemporanea... Anche qui puoi risolvere in due modi:
2.a) Elimina il campo identity e gestisci la chiave a manina (che trall'altro nel caso della fatturazione è anche l'unico modo visto che il numero della fattura si resetta ogni anno, è quindi sono chiave numero e data o numero e anno)
2.b) lavora nella transaction, salva la testata, recupera l'id generato e usalo per il dettaglio
Spero ti sia stato utile, se hai ancora problemi puoi mandarmi il tuo codice, gli do volentieri un'occhiata.
Per la tua gioia il codice è in VB, così te lo devi tradurre in C# (un po' di esercizio non fa mai male...)
A presto.
transaction.Commit()
Rettifico, la connection è già pubblica, quindi ancora più semplice...
Abbiamo scoperto che in visual studio 2008 è stato introdotto il TableAdapterManager, che risove la maggior parte dei problemmi discussi. A breve il nostro Nezumi, che lo sta testando, ci illuminerà!!!!
Citando MSDN
TableAdapterManager è un nuovo componente di Visual Studio 2008 che si basa su funzionalità dei dati esistenti, quali dataset tipizzati e TableAdapter, e fornisce la funzionalità per salvare i dati nelle tabelle dati correlate. TableAdapterManager utilizza le relazioni di chiavi esterne relative alle tabelle dati per determinare l'ordine corretto di invio dei comandi di inserimento, aggiornamento ed eliminazione da un dataset al database senza violare i vincoli di chiave esterna (integrità referenziale) nel database.
Metodo UpdateAll
Salva tutti i dati presenti in tutte le tabelle dati.
Proprietà BackUpDataSetBeforeUpdate
Boolean. Determina se creare una copia di backup del dataset prima di eseguire il metodo TableAdapterManager.UpdateAll.
Nometabella Proprietà TableAdapter
Rappresenta un TableAdapter. Il TableAdapterManager generato contiene una proprietà per ogni TableAdapter gestito. Ad esempio, un dataset con una tabella Customers e Orders viene generato con un TableAdapterManager contenente le proprietà CustomersTableAdapter e OrdersTableAdapter.
Proprietà UpdateOrder
Controlla l'ordine di esecuzione dei singoli comandi di inserimento, aggiornamento ed eliminazione. Impostare questa proprietà su uno dei valori dell'enumerazione TableAdapterManager.UpdateOrderOption.
Per impostazione predefinita, la proprietà UpdateOrder viene impostata su InsertUpdateDelete. Ciò significa che i comandi di inserimento, aggiornamento ed eliminazione vengono eseguiti per tutte le tabelle del dataset esattamente in questo ordine. Per ulteriori informazioni, vedere Procedura: impostare l'ordine per l'esecuzione di un aggiornamento gerarchico.
in pratica supponendo di avere il dettaglio di un entità e di una a cui si relazione
basterà sulla chiusura di un form (o in qualsiasi altra posizione decidiate di salvare)
scrivere:
niente di più facile credo... no??? alla prox
Associazione Culturale DotNetCampania - C.F.: 95127870632