LINQ? ADO.NET? Confusione completa! [Lungo]

giovedì 28 gennaio 2010 - 10.50

disti Profilo | Newbie

Buondì,

dopo aver sviluppato per anni software con VB6+ADO, ora sto tentando il passaggio a .NET. Per quanto riguarda gli aspetti generali nessun problema: penso che il nuovo linguaggio sia DAVVERO più bello, più coerente e più flessibile.

Per quanto riguarda i database invece ho continui problemi. In particolare, non sono certo che le scelte implementative che faccio siano le più azzeccate, ovvero temo che ci siano metodi più corretti per fare quello che mi interessa.

Ora in particolare mi ritrovo ad affrontare il seguente problema:

[Tabella anagrafica_voci_costo]
IDVoceCosto (PK)
Descrizione
AmbitoApplicazione
Cumulabile

[Tabella voci_costo_pratiche]
IDPratica (PK)
IDVoceCosto (PK)
Ordine (PK)
Importo

le due tabelle sono legate ovviamente dal campo IDVoceCosto.

Il problema da risolvere è credo uno dei più comuni del mondo: data una pratica voglio recuperare le relative voci costo, con, per ciascuna, descrizione, ambito di applicazione e cumulabilità (dalla prima tabella) e importo (dalla seconda).
Dopo di che voglio visualizzare tutto ciò in una tabellina, con possibilità di inserire/modificare/cancellare righe, aggiornando infine il db.

Per leggere le righe in VB6 avrei avuto una connessione aperta al db, per cui avrei scritto un Command di selezione con una query SQL, dopo di che avrei disconnesso il recordset risultante e mi sarei messo a modificarlo.

Alla fine avrei eliminato dal DB tutte le righe costo relative alla pratica in uso e le avrei reinserite prendendole dal recordset modificato.

In .NET non so come fare questa stessa cosa:
ho provato con LINQ, ma ho scoperto che se viene utilizzato per selezionare da più tabelle il risultato è in sola lettura (in questo articolo: http://blogs.msdn.com/swiss_dpe_team/archive/2008/01/25/using-your-own-defined-type-in-a-linq-query-expression.aspx viene citata la modifica dei dati, ma non ho avuto successo nel riprodurre l'esempio proposto - funziona ma non aggiorna).

Ho visto che è possibile utilizzare comandi SqlConnection e SqlCommand, ma non sono certo di quale sia l'implementazione formalmente più corretta.

Poi ci sono i dataset....

Ho anche acquisatato qualche libro sull'argomento, ma purtroppo appena ci si discosta dagli esempi più banali non sono più di molto aiuto...

Qualcuno è in grado di fornirmi le linee guida più corrette per risolvere questo problema? Un po' di codice di esempio è graditissimo , ma soprattutto mi interessa capire l'approccio generale al discorso.


grazie a tutti!!!


Roberto

tonyexpo Profilo | Senior Member

Ciao Roberto
e benvenuto in .NET ;)

la tua domanda è legittima, ma anche abbastanza complessa per via della sua vastità. spero di scrivere una panoramica sull'argomento così da chiarire un po le cose.....

le tecniche e le classi di accesso ai dati in .NET sono varie, alcune più mature altre meno, ma nell'insieme danno la possibilità di coprire ogni scenario di utilizzo

Il tuo contesto (accedere a più tabelle mappate) è molto comune, e per essere gestito in modo nativo con degli strumenti del .NET dovresti usare o i DataSet (ADO.NET Disconnesso) o LINQ o EntityFramework.

Alternativamente puoi fare le cose a mano con l'ADO.NET Connesso utilizzando combinatamente gli oggetti DBConnection, DBCommand etc... per fare gli statement necessari


Il mio consiglio è usare LINQ2SQL

Fai Add -> Linq 2 Sql Classes
Ti connetti al DB con ServerExplorer e trascini le tue 2 tabelle.... Le tabelle devono avere già un riferimento impostato sul DB tra di loro

Vai nel tuo codice e scrivi:
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


se qualcosa non è chiara, chiedi pure
ciao

Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Grazie Antonio per la risposta!

L'approccio da te suggerito purtroppo non è molto amico delle griglie. Immaginiamo che le tabelle che ho descritto nel post precedente contengano i seguenti dati:

[Tabella anagrafica_voci_costo]
IDVoceCosto (PK);Descrizione;AmbitoApplicazione;Cumulabile
0;"Quota fissa";0;True
1;"Sconto fedeltà";0;False
2;"Sconto nuovi clienti";0;False

[Tabella voci_costo_pratiche]
IDPratica (PK);IDVoceCosto (PK);Ordine (PK);Importo
0;0;0;100.00
0;1;1;10.00

se io per esempio scrivo
Dim voci=From v in DataContext.voci_costo_pratiche Where v.IDPratica=0 Select v

avrò un oggetto voci di tipo IEnumerable(Of voci_costo_pratiche) che contiene le proprietà:
IDVoceCosto
Descrizione
AmbitoApplicazione
Cumulabile
anagrafica_voci_costo

Quest'ultima proprietà contiene, per ogni singola riga della tabella voci_costo_pratiche, il relativo record della tabella anagrafica_voci_costo.

Tutto questo è molto bello (e infatti ho già sfruttato tutto ciò in altre parti del progetto), ma purtroppo l'oggetto "voci" così ottenuto non può essere utilizzato per popolare una griglia in un form del tipo:

|Descrizione voce|Cumulabile|Importo|

nella quale io possa modificare la proprietà "Importo", aggiungere righe, rimuovere righe.

Il risultato più prossimo che ho ottenuto è stato utilizzando una query linq come la seguente (creando una classe ad hoc per il risultato):

dim voci as ienumerable(of MyResultClass)=from v in dc.voci_costo_pratiche where v.idpratica=0 select new myresultclass with { Descrizione=v.anagrafica_voci_costo.descrizione, Cumulabile=v.anagrafica_voci_costo.cumulabile, Importo=v.Importo}

il risultato può essere effettivamente assegnato a una griglia, ma purtroppo non supporta l'aggiornamento.

Nell'articolo che ho citato nel post precedente si intuisce che un approccio di questo tipo dovrebbe funzionare (vedi commento dell'autore subito sotto al codice) ma purtroppo a me non va...

ciao!

Jeremy Profilo | Guru

Ciao.
prova a cambiare approccio .....
invece di :
dim voci as ienumerable(of MyResultClass)=from v in dc.voci_costo_pratiche where v.idpratica=0 select new myresultclass with { Descrizione=v.anagrafica_voci_costo.descrizione, Cumulabile=v.anagrafica_voci_costo.cumulabile, Importo=v.Importo}

prova con:
dim voci as list(of MyResultClass)=(from v in dc.voci_costo_pratiche where v.idpratica=0 select new myresultclass with { Descrizione=v.anagrafica_voci_costo.descrizione, Cumulabile=v.anagrafica_voci_costo.cumulabile, Importo=v.Importo}).tolist

In questo modo hai a disposizione una lista di tipo System.Collections.Generic, la quale,dovrebbe supportare le caratteristiche che servono a te.
Facci sapere....
Ciao

disti Profilo | Newbie

Grazie Jeremy,

avevo provato. L'oggetto che ottengo è effettivamente aggiornabile, ma poi quando richiamo "dc.submitchanges()" le modifiche che ho apportato alla lista non vengono salvate nel db.

il mio problema è che non riesco a capire se è completamente sbagliato l'approccio o se semplicemente c'è qualcosa di stupido che mi sfugge.

Per esempio un po' di tempo fa avevo problemi con una classe creata da me e utilizzata con linq: non funzionava correttamente ma non veniva generato alcun errore. Alla fine è venuto fuori che una delle proprietà era dichiarata NOT NULLABLE mentre nel db era NULLABLE e questo dava problemi anche quando non entravano in gioco valori NULL. Ci ho messo tre giorni a capire cos'era...

ciao!

tankian Profilo | Junior Member

Ciao, io personalmente uso ado.net e non ho mai provato linq.


La domanda che mi faccio è:

utilizzando linq, si guadagnano performance in lettura dei dati? (dataadapter per popolare DGV e datareader)

e nella manipolazione? (command.executenonquery)

disti Profilo | Newbie

Io trovo che linq sia molto potente e utile in diverse circostanze, comincio a pensare che il problema maggiore sia proprio capire quando invece conviene NON tentare di utilizzarlo...

tonyexpo Profilo | Senior Member

Ho capito il tuo problema

tu vuoi una griglia aggiornabile ma vuoi utilizzare colonne di 2 entità in JOIN tra di loro
questo caso ti creerà sempre problemi sia che tu usi DataSet che LINQ questo perchè l'oggetto che tu usi non è più quello del contesto (nel caso di LINQ) o tantomeno il datarow originale nel caso del DataSet

ti consiglio di usare sempre linq, fare una classe come già hai fatto per mappartici dentro gli elementi che vuoi ma in questo modo:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

quando poi farai il context.SubmitChanges() automaticamente gli oggetti saranno aggiornati in modo corretto
ricorda solo che il context deve essere shared o comunque lo stesso che hai utilizzato per creare l'oggetto entità originale, altrimenti dovrai usare anche context.MyEntities.Attach(Entità) prima di SubmitChanges per riagganciare l'entità originale alla tabella del contesto



fammi sapere se qualcosa non è chiaro
ciao


Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Grazie Antonio,

è confortante sapere che qualcuno ha una soluzione per me!

Non mi è chiaro a cosa si riferiscono "Entità" e "MiaEntità" nell'esempio che mi hai scritto.
Ti dispiace spiegarmelo magari facendo riferimento al mio esempio? Scusa se rompo, ma essendo la prima volta che affronto la cosa vorrei capirla bene!

Grazie, ciao!

Roberto

tonyexpo Profilo | Senior Member

Di niente



ti faccio un esempio più completo:

aggiungi le tue classi linq2sql
colleghi 2 tabelle: Persone e Ordini in realzione 1-N (una persona può avere più ordini)


ti crei una classe per rappresentare la tua proiezione (la join) e in quella classe metti la logica del mapping

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra



adesso ovviamente arriva la parte meno facile:
se usi il LINQDataSource, fai subito a collegare i dati
internal shared context as new MyDBDataContext() private sub LDS_Selecting(sender as object, e as LinqDataSourceSelectedEventArgs) e.Result = from o in context.Ordini select new OrdineEPersona(o) end sub
ma ti perdi l'update perchè anche se colleghi una tabella del context al LinqDataSource questa non è quella realmente passatagli nel Selecting


ti consiglio allora di fare la gridview a mano e gestire con le colonne e i template il rowEditing
una volta mappato correttamente l'oggetto, dovresti fare semplicemente
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra
alla pressione del tasto UPDATE




spero di essere stato chiaro
altrimenti appena posso posto un esempio funzionante
ciao

Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Passi avanti!!

ora ho del codice che mi permette di caricare i dati già presenti nel database e di apportare modifiche. Utilizzando submitchanges le modifiche vengono scritte sul db. Evvai!

Non capisco però perchè le funzioni di eliminazioni e inserimento non funzionano: utilizzando le due funzioni che ho scritto i dati vengono effettivamente eliminati e/o aggiunti all'oggetto offline, ma la chiamata a submitchanges non funziona (anche se NON da errrori).

Posto qui l'intero codice che ho scritto.

ciao, grazie!!!!

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

disti Profilo | Newbie

Ho fatto qualche progresso in più:

intanto ho aggiunto in form_load il comando "dc.Log = Console.Out" in modo da vedere le query generate da LINQ. Ho così scoperto che in caso di inserimento/eliminazione non venivano generate query.

Ho in parte capito dov'è il problema anche se a questo punto il funzionamento complessivo di LINQ non mi è chiaro:

l'oggetto "voci" del mio codice sembra non avere collegamenti con il datacontext per quanto riguarda l'inserimento/eliminazione delle righe, mewntre ce l'ha per la modifica (?????).

Per quanto riguarda l'inserimento ho (quasi) risolto come segue: ho modificato così la funzione "cmdAggiungiRiga_Click"

Dim vc As New sgv_Viaggi_VociCosto vc.IDViaggio = 0 vc.IDVoceCosto = 3 vc.Ordine = 123 vc.Valore = 0 vc.sgv_AnaVociCosto = New sgv_AnaVociCosto vc.sgv_AnaVociCosto.IDVoceCosto = 0 vc.sgv_AnaVociCosto.Azienda = 0 vc.sgv_AnaVociCosto.Descrizione = "prova inserimento" vc.sgv_AnaVociCosto.Unita = 0 vc.sgv_AnaVociCosto.SiApplicaA = 0 vc.sgv_AnaVociCosto.ValePer = 0 vc.sgv_AnaVociCosto.Cumulabile = 0 dc.sgv_Viaggi_VociCosto.InsertOnSubmit(vc) bs.Add(New VociCosto(vc)) bs.EndEdit() grdVociCosto.Update() vc = Nothing

richiamando il metodo "insertonsubmit" quando chiamo "submitchanges" viene effettivamente generato il codice SQL per l'inserimento.

A questo punto però mi si presenta un problema: in fase di submit LINQ cerca di inserire una riga nuova anche in sgv_AnaVociCosto, che però non deve essere creata in quanto deve fare riferimento a una riga già esistente!!!

Come si potrà fare?

Grazie ancora Antonio, come vedi mi sto applicando

disti Profilo | Newbie

Niente, non mi riesce di uscire dal tunnel...

nell'esempio che mi hai postato l'ultima volta c'erano queste due righe:

context.Names.Attach(tempName) 'dove tempname è quella che hai mappato alle colonne del gridview
context.SubmitChanges()

il problema è che io relativamente all'oggetto contezt non ho nessuna proprietà names... come mai?

Grazie Antonio!

tonyexpo Profilo | Senior Member

Ciao

ti ho preparato un esempio:

troverai una paginetta aspx, uno script .sql per creare il db come l'ho creato io (ovviamente è 1 esempio)


come ho proseguito per fare tutto nel modo più facile:
ho creato 2 tabelle collegate
poi le ho mappate in LINQ2SQL trascinandole da server explorer

poi ho fatto una partial class nel file del TestDBDataContext per aggiungere una proprietà "Cliente:string" nel tipo Ordine così da averla disponibile per la gridview in sola lettura.

Poi il LINQDataSource ha fatto tutto il resto, lettura e scrittura incluse ;)
se vuoi invece customizzare queste 2 funzionalità, basta intercettare gli eventi Selecting e Updating sul LINQDataSource e customizzare il gridview come ti avevo già accennato


facci sapere
ciao



Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Intanto Antonio ti ringrazio davvero tanto per il tempo che mi dedichi!

Se guardi un paio di miei post più in su vedi che in effetti ero riuscito a fare in modo che il mio programma aggiornasse i valori, un po' come fa l'esempio che mi hai mandato.

Purtroppo però non riesco ad aggiungere e eliminare!

Non sto a riscrivere le prove che ho fatto, se hai voglia di darci un'occhiata ho riportato il mio codice attuale tre e quattro post più su!

grazie ancora

Roberto

tonyexpo Profilo | Senior Member

OPS... mi sono scordato di farti gli esempi per l'eliminazione e l'aggiunta.....

sono in ufficio e qui non posso fare molto, sta sera vedo di aggiornarti l'esempio

ciao

Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Non vorrei rovinarti la serata: se hai di meglio da fare, fallo!
In caso contrario, be', grazie di cuore!

tonyexpo Profilo | Senior Member

Ciao
Nessun problema ;)


per l'eliminazione dell'elemento dalla griglia, devi solo andare sul linqdatasource e impostare la property EnableDelete a true, lo stesso sul gridview se l'hai collegato al linqdatasource, c'è un pulsantino in alto a destra del controllo si chiama "GridView Tasks", li dentro c'è un checkbox con "enable deleting"
una volta checcati il delete funziona con il classico click sulla scritta delete

ma ti avverto: linq è sincrono, e l'eliminazione è senza conferme........

per l'inserimento ci si deve scontrare con un altro problema: il gridview non ci viene in aiuto in quanto a differenza del DataGrid (la vecchia griglia) il gridview non supporta l'aggiunta..... se cerchi in giro c'è qualcuno che ci riesce simulando dei controlli nel footer... ma nn so quanto siano affidabili, magari qualcuno che l'ha fatto ci legge e scriverà la sua.

per l'aggiunta ti consiglio quindi di fare semplicemente tutto dall'esterno, fai un nuovo oggetto, lo popoli in modo corretto (soddisfacendo i constraint del db - gli indici/chiavi) e poi lo aggiungi al datacontext facendo <datacontext>.<tabella>.InsertOnSubmit, e poi gestendoti il ri-DataBind della griglia per far vedere il nuovo elemento... ovviamente non è bello come una volta con i DataGrid......

altimento, il modo più comune forse, è quello di creare una altra pagina per l'inserimento dei dati (una pagina di dettaglio) e poi salvarli con un semplice pulsante.... la griglia dovrebbe essere infatti solo informativa e non dispositiva


ciao

Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Anche se ho ancora qualche problemino secondario, direi che ormai ci siamo.

Grazie Antonio, mi hai dato un aiuto davvero prezioso!

ciao!!

tonyexpo Profilo | Senior Member

Di niente ;)

ciao

Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Ciao,

Il mio codice ora funziona bene per quanto riguarda inserimento e modifica, ma purtroppo ho problemi con l'eliminazione!

Ho scritto questa procedura di prova che dovrebbe creare una voce, eseguire il commit, e poi eliminarla:

'voci è definita altrove: "Dim voci As List(Of sgv_Viaggi_VociCosto)" voci = (From v In dc.sgv_Viaggi_VociCosto Where v.IDViaggio = 1 Select v).ToList 'Trovo il primo numero di riga libero per la nuova voce Dim NuovoOrdine = CByte((From v In voci Select v.Ordine).Max + 1) 'Creo l'oggetto da inserire Dim vc As New sgv_Viaggi_VociCosto With {.IDViaggio = 1, _ .IDVoceCosto = 1, _ .Ordine = NuovoOrdine, _ .Valore = 0} 'Ricavo la riga della tabella sgv_AnaVociCosto da cui dipende la mia nuova voce Dim anavc = (From v In dc.sgv_AnaVociCosto Where v.IDVoceCosto = 1 Select v).Single 'Aggiungo l'oggetto che ho creato anavc.sgv_Viaggi_VociCosto.Add(vc) dc.SubmitChanges() 'FIN QUI FUNZIONA!!!!! MsgBox("pausa...") 'Rimuovo l'oggetto che ho creato anavc.sgv_Viaggi_VociCosto.Remove(vc) dc.SubmitChanges() 'QUI INVECE HO UN ERRORE :( MsgBox("OK.")

Eseguendo questa procedura, ricevo il seguente errore:

System.InvalidOperationException verificata Message="Si è tentato di rimuovere una relazione tra un sgv_AnaVociCosto e un sgv_Viaggi_VociCosto. Tuttavia non è possibile impostare una delle chiavi esterne (sgv_Viaggi_VociCosto.IDVoceCosto) della relazione su null."


PERCHE' !!!!!!!????????

tonyexpo Profilo | Senior Member

Ciao

credo il problema sia che viene interpretato come un cambio di chiave esterna (in pratica stai togliendo la vocecosto dall'anagrafica senza assegnargliene un'altra e questo viola il vincolo di chiave esterna appunto)

nel tuo caso lo vuoi proprio eliminare quindi:

dc.Viaggi_VociCostos.DeleteOnSubmit(vc)
dc.SubmitChanges()


prova così e facci sapere
ciao

Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos

disti Profilo | Newbie

Esatto!

così funziona, decisamente non ho ancora dimestichezza con l'argomento.

Grazie infinite, come al solito!!!

tonyexpo Profilo | Senior Member

di niente ;)
Antonio Esposito
MCTS, MCP

http://blogs.dotnethell.it/espositos
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5