di Enrico Sabbadin A qualcuno è sfuggito questo ?
ADO.NEt VNEXT:
http://msdn.microsoft.com/data/ref/adonetnext/default.aspx
Forse finalmente un tool di O/R (object Relational mapping) fornito da MS.. dopo tante false partenze (ricordate Object Spaces) ?
Non è ben chiaro quando sara rilasciato, direi non con .NET 3.0, per quanto ho potuto capire.
Vi segnalo questi due screencast per farvi venire l'acquolina in bocca .. buona visione.
http://datajunkies.net/screencasts/adonet_vnext_part1/adonet_vnext_part1.html
di Marco BellinasoPer il progetto sul quale stò lavorando c'era la necessità di produrre un PDF sulla base di un report HTML abbastanza complesso, con griglie, immagini, CSS interni all'HTML stesso o in file esterni linkati. Abbiamo provato per giorni ad usare un certo prodotto commerciale piuttosto famoso che fa tutto ciò, ma richiedendo di creare account Windows particolari, configurare in modo appropriato addirittura i browser che stà sul server, creare varie cartelle, configurare l'apertura di porte particolari ecc. ecc. Insomma, bisogna dedicare un server a questo tool, e anche così bisogna perdere del tempo per sistemare il tutto. Stanchi di queste difficoltà per il test e il deployment, ieri mi sono messo a cercare un'alternativa. Risultato :ABCpdf! Questo componente fa davvero tutto quello che ci serve, in modo ottimale a quanto sembra per ora, e la sua installazione e utilizzo sono di una semplicità disarmante: basti dire che in poco più di un'ora ho scaricato e installato il prodotto, mi sono visto un paio di loro esempi, ho riscritto il servizio di generazione di PDF e ho testato che funzionava bene... Insomma, forse l'altro componente sarà anche in grado di svolgere compiti più complessi (fa da vero e proprio server PDF, mettendo in coda le richieste e smaltendole in modo asincrono), ma se non avete necessità di gestire carichi davvero enormi, e non avete un server da dedicarci, ABCpdf è molto veloce e può fare al caso vostro. Tra l'altro, il componente è gratuito se mettete sul sito un link al produttore (altrimenti c'è una licenza di prova di 30 giorni oltre i quali si dovrà comprare la licenza commerciale)!
Colgo l'occasione di questo post per segnalare anche che nel blog in inglese ho appena pubblicato un nuovo estratto dal mio libro, che mette a confronto le varie tecnologie MS per gestire le transazioni distribuite e non all'interno di applicazioni .NET: transazioni ADO.NET, COM+/SWC e System.Transactions. Il tutto in modo piuttosto conciso (del resto è solo una delle tante cose che introduco nel capitolo 3, riguardanti i fondamenti della progettazione del sito), non è certamente intesa come una trattazione esausitva ma credo possa dare velocemente un'idea delle varie possibilità a chi ancora non ci ha avuto a che fare.
di Enrico SabbadinVi sarà capitato di vedere, in del codice di esempio o generato da Wizards, l'utilizzo del motodo GetChanges del Dataset per estrarre un Dataset con solo le righe modificate. Tale Datsaet veniva mandato al Business Layer potenzialment remoto invece che tutto il DataSet per risparmiare banda.
Evidentemente questa cosa non mi ha mai convinto: se il mio Dataset era fatto da una Table Ordine e una Table RigaOrdine, mi sarei ritrovato sul server degli "oggetti" ordine incompleti .. come avrei potuto implementare logiche che richiedevano per esempio un numero minimo di righe nell'ordine ?
Quando uno usa i Dataset per contenere cose che sono logicamente "oggetti" fatti di elementi in relazione gerarchica tra loro, ha bisogno di una GetChanges "Object Oriented" che estrae tutti gli ordini che hanno la testata o una delle righe modificate (che vuol dire aggiunte, editate o cancellate).
Questo è un requisito non solo per implementare correttamente logiche di business ma anche per implementare funzionalità logicamente proprie di un oggetto all'interno del proprio business layer : specificatamente con una GetChanges "object oriented" ho potuto facilmente implementare una politica di update basata su timestamp non della singola riga ma di tutto l'oggetto (se modifico una riga dell'ordine cambio il timestamp della testa ordine). Un altra cosa che ho potuto fare facilmente è salvare una copia di "backup/revisione/storia" (un po' alla source safe) di ogni oggetto che gestisco ogni volta che un elemento a qualunque livello della gerarcia dell'oggetto è modificato.
Qui di seguito trovate il codice per implementare la mia GetChanges, chiaramente funziona non solo con un solo livello di gerarchia ma con una struttura di tabelle gerachicamente "innestate" a piacimento.
Il codice non è il massimo dell'eleganza anche per la necessità di scrivere codice "particolare" per gestire (navigare, estrarre) le righe in stato deleted.
public DataSet GetFullChanges(DataSet p_Originalds,string p_rootTable) { DataSet _Smartds = (DataSet)Activator.CreateInstance(p_Originalds.GetType()); // cosi' mentre costruisco il Dataset non devo preoccuparmi // dell'integrità referenziale _Smartds.EnforceConstraints = false;
DataTable _DataTable = p_Originalds.Tables[p_rootTable]; ArrayList _ModifiedObjects = new ArrayList(); // voglio trovare tutti i padri che // sono stati modificati o cancellati o hanno dei figli cancellati o modificati foreach (DataRow _Row in _DataTable.Rows) if (pf_ThisOrChildrenHaveChanges(_Row)) _ModifiedObjects.Add(_Row);
foreach (DataRow _Row in _ModifiedObjects) { pf_ImportRowAndChildren(_Smartds, _Row); }
_Smartds.EnforceConstraints = true; return _Smartds; }
private bool pf_ThisOrChildrenHaveChanges(DataRow p_Row) { // la riga è modificata esco if (p_Row.RowState != DataRowState.Unchanged) return true;
foreach (DataRelation _Rel in p_Row.Table.ChildRelations) { // original mi da anche le cancellate ma non le nuove, // devo quindi chiederla due volte con parametro diverso // al primo giro la uso solo per trovare le figlie cancellate DataRow[] _ChildRows = p_Row.GetChildRows(_Rel, DataRowVersion.Original); foreach (DataRow _row in _ChildRows) if (_row.RowState == DataRowState.Deleted) return true;
// adesso la uso per esaminare ecursivamente le righe figlie (nuove o modificate) _ChildRows = p_Row.GetChildRows(_Rel, DataRowVersion.Current); foreach (DataRow _row in _ChildRows) if (pf_ThisOrChildrenHaveChanges(_row)) return true; } // se arrivo qui la riga non è modificata, ne lo è uno dei suoi figli return false; }
// importo dai figli a salire verso il padre // anche se non è strettamento necessario poichè sono con // enforceconstraints = false private void pf_ImportRowAndChildren(DataSet p_TargetSmartDS, DataRow p_SourceRow) { p_TargetSmartDS.Tables[p_SourceRow.Table.TableName].ImportRow(p_SourceRow); foreach (DataRelation _Rel in p_SourceRow.Table.ChildRelations) { DataRow[] _ChildRows = null; if (p_SourceRow.RowState == DataRowState.Deleted) _ChildRows = p_SourceRow.GetChildRows(_Rel, DataRowVersion.Original); else _ChildRows = p_SourceRow.GetChildRows(_Rel);
foreach (DataRow _row in _ChildRows) { // ricorsivamente importo la riga ed i suoi figli pf_ImportRowAndChildren(p_TargetSmartDS, _row); } } }
di Marco BellinasoUna delle novità più sbandierate sia di SQL Server 2005, ma soprattutto di ASP.NET 2.0 è la possibilità di fare caching con dipendenze direttamente ai dati contenuti nel DB. Ovvero: tengo i dati in cache finchè non cambiano sul DB, invece che per un tempo determinato. Questo permette di risolvere 2 problemi della cache attuale:
- Richiedere nuovamente i dati dal DB anche se in realtà non sono stati chiamati
- I dati vengono cambiati poco dopo che sono stati messi in cache...ci resteranno magari per altri tot minuti, fornendo quindi dati vecchi all'applicazione
Troppo bello per essere vero, no? Beh, vediamo...Intanto c'è da dire che il meccanismo funziona in modo diverso con SQL Server 7/2000 e con SQL Server 2005. Nel primo caso l'abilitazione al caching di ASP.NET con dipendenza ai DB deve essere fatta a mano, tramite il tool aspnet_regsql da riga di comando. Lo si chiama 2 o più volte: una prima per abilitare il supporto a livello di DB, e poi una volta per ciascuna tabella. Quello che questo tool fa è di creare delle nuove tabelle di supporto, trigger e stored procedure che tengono traccia delle modifiche (insert/update/delete) fatte sulle tabelle tenute sott'occhio. ASP.NET poi implementa un meccanismo di polling che ogni tot millisendi (la frequenza è configurabile da web.config) verifica se i dati in cache sono ancora validi, controllando se la tabella di supporto indica o meno che i dati sono cambiati dall'ultima query (internamente una stored procedure controlla se il contatore per quella tabella è stato incrementato dall'ultima volta). Da notare che la dipendenza è a livello di intera tabella, ovvero se io salvo in cache un record di una tabella, e qualcuno cambia un altro record della stessa tabella, la cache viene comunque invalidata.
Per quando riguarda SQL Server 2005 le cose sono molto diverse. Il nuovo engine è in grado di creare una indexed view per ogni query eseguita (che a differenza di una vista normale è una copia fisica dei valori), per la quale si è specificato che si vuole creare una dipendenza. Quando i dati restituiti dalla query eseguita cambieranno (sempre in seguito ad una insert/update/delete, ma anche altri comandi) SQL Server manderà una notifica al client che si era in precedenza registrato, spedendogli un messaggio tramite un nuovo servizio chiamato Service Broker. Tutto questo nuovo meccanismo si chiama Query Notifications. E' molto interessante, perchè permette di venire notificati solo quando i risultati della nostra specifica query cambierebbero se la query venisse eseguita nuovamente, cosa che quindi non accadrebbe se qualcuno modifica un record che non era incluso nei risultati precedenti. Se volete maggiori dettagli su come funziona il tutto dietro le quinte, potete fare riferimento a quest'ottimo articolo di Bob Beauchemin. In .NET la classe che permette di ricevere tali notifiche è SqlDependency, tramite il suo evento OnChange. Una volta che si ha un comando SqlCommand, ecco come usarla:
SqlDependency dep = new SqlDependency(cmd); dep.OnChange += new OnChangeEventHandler(dep_OnChange); ... void dep_OnChange(object sender, SqlNotificationEventArgs e) { // ... }
La classe SqlDependency è una classe di ADO.NET, e può quindi essere usata da qualsiasi applicazione. In ASP.NET poi c'è la nuova classe SqlCacheDependency, che internamente usa SqlDependency per sapere quando i risultati del commando ricevuto in input nel costruttore sono cambiati, e quindi invalidare l'elemento della Cache a cui fa riferimeno. Il tutto si traddurrebbe semplicemente come segue:
SqlCacheDependency dep = new SqlCacheDependency(cmd); Cache.Insert("Categories", dsCategories, dep);
Fatta la dovuta introduzione, veniamo al punto del post...Per il sito che stò sviluppando al momento stò usando proprio SQL Server 2005, Express Edition. Ovviamente dopo aver letto articoli e articoli sulla cache, averne sentito parlare dappertutto ecc. ecc. mi sentivo anch'io parecchio eccitato a proposito, e non vedevo l'ora di usarla. Bene, ci provo e...non funziona! Il codice di test che ho isolato, usando direttamente SqlDependency in una applicazione WinForm, era il seguente:
private void Form1_Load(object sender, EventArgs e) { using (SqlConnection cn = new SqlConnection(@"Data Source=.\SQLEXPRESS; AttachDbFilename=C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\northwnd.mdf; Integrated Security=True;User Instance=True")) { SqlCommand cmd = new SqlCommand("SELECT * FROM Categories", cn); cn.Open(); SqlDependency dep = new SqlDependency(cmd); dep.OnChange += new OnChangeEventHandler(dep_OnChange); SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read()) textBox1.Text += reader["CategoryName"] + Environment.NewLine; } }
void dep_OnChange(object sender, SqlNotificationEventArgs e) { MessageBox.Show("Changed!"); }
Accadeva che l'evento veniva sollevato subito dopo aver eseguito il comando...ma non perchè i dati fossero cambiati, per qualche altro motivo. Ho cercato in lungo e in largo, fino a quando il grande Alex Homer (quello che ha insegnato ASP2 a generazioni di sviluppatori web e autore tra l'altro di questo articolo proprio sulla cache) ha risposto ad una mia mail di richiesta spiegazioni. Nel mio codice c'erano fondamentalmente due errori:
- Non è possibile usare * nelle query, ma bisogna elencare tutti i campi esplicitamente
- Il nome delle tabella deve essere completo, ovvero qualcosa tipo dbo.Categories
Nella stored procedure che usavo nel mio caso reale indicavo già tutti i campi esplicitamente (sembre una buona regola), ma in effetti non usavo il nome completo della tabella. Queste comunque sono solo 2 delle molte regole che bisogna rispettare se si vuole poter usufruire delle Query Notifications. Le altre sono indicate nei Books Online di SQL Server 2005...oppure più semplicemente le trovate elencate in questa pagina. Le limitazioni sono davvero tante e significative: niene funzioni di aggregazione come COUNT e SUM, niente funzioni di ranking e paginazione (come la nuova ROW_NUMBER()), niente uso di tabelle temporanee, niente DISTINCT, niente query che restituiscono colonne di tipo text, ntext, o image, e altro ancora...In aggiunta ho scoperto (altra ora di tentativi ovviamente...) che la mia stored procedure non funzionava anche per un altro motico: avevo impostato SET NOCOUNT ON...
Ora, considerando che uso pesantemente la paginazione custom per i vari controlli GridView ecc. di ASP.NET, che ho bisogno di COUNT(*) sempre per supportare la paginazione, che molte delle mie tabelle hanno colonne ntext...praticamente il risultato è che non posso usare SqlCacheDependency da nessuna parte!!! Mi devo accontentare ancora del "vecchio" caching temporizzato, implementando in aggiunta un metodo per fare manualmente il purge dei dati in cache dopo aver eseguito una Insert / Update o Delete tramite i miei oggetti business. Non ho dubbi che ci siano molti casi di utilizzo perfetti, ma molto probabilmente molti meno di quanto tanti cercano di far credere in molti articoli e conferenze. Ah, un'ultima nota (anch'essa poco pubblicizzata), nel caso vogliate usare questa feature. Le SQL Dependencies di SQL Server 2005 NON dovrebbero ASSOLUTAMENTE essere utilizzate in almeno 2 casi:
- Se la tabella in questione viene aggiornata molto spesso. Ad esempio, nella mia tabella Articles c'è una colonna ViewCount che indica quante volte l'articolo è stato letto, e che quindi viene aggiornata ad ogni lettura. Il risultato sarebbe che la cache verrebbe invalidata praticamente ad ogni richiesta di un articolo...non solo annullandone l'effetto positivo, ma peggiornado parecchio la situazione, a causa del lavoro aggiuntivo per salvare i dati in cache, gestire la coda dei messaggi di notifica (in entrata e uscita) e pulire la cache...tutto per niente.
- Per query dinamiche risultanti da tanti filtri, e magari anche dipendenti dall'utente corrente. Questo meccanismo infatti non è fatto per poter supportare centinaia o migliaia di query diverse sullo stesso DB. Va bene se lo usate per una stored procedure che magari prende in input qualche filtro, ma che viene comunque chiamata allo stesso modo da gran parte degli utenti.
Detto questo, buoni esperimenti e buon divertimento 
Acknowledgements: thank you Alex for your kind explanation, you made my day! And thank you Bob for the great article on MSDN.
di Marco BellinasoO almeno questo è il titolo di questa pagina su MSDN In realtà per ora io ne ho contate 47 di applicazioni di esempio, ma forse ne ne sono perse 3 perchè in fondo alla pagina c'è scritto "Coming soon - The remaining 51 samples are coming soon. Stay tuned." 
Ma non andiamo a pignolare, anche quello che c'è già è decisamente benvenuto! Le applicazioni sono fornite in C# o VB.NET e sono divise nelle seguenti categorie:
- Base Class Libraries: come cambiare le ACL di un file, implementare animazioni su console, scaricare file da FTP, comprimere e decomprimere file, usare le generic collections, ricavare informazioni sui drive, usare la classe Stopwatch, eseguire il PING e qualche altra operazione di networking.
- Data Access: query asincrone, uso e confronto di Datareader con DataSet, update batch, paginazione, bulk update, salvataggio e recupero di immagini dal DB, classi factory, uso di MARS, Notification Services, e UDT di SQL Server 2005.
- Web Development: master pages, API e controlli di membership, profili, controlli menu e TreeView, web part, data binding con i vari componenti xxxDataSource.
- Windows Forms: task asincroni, componenti BindingNavigator e BindingSource, estensioni alla DataGridView standard, i nuovi controlli MaskedTextBox, WebBrowser, StatusStrip, ToolStrip, SplitContainer e LayoutPanel.
Insomma, se ancora non avete dato un'occhiata a .NET 2.0 e VS.NET 2005, questi esempi pronti per essere eseguiti sono un'ottima occasione per vedere velocemente alcune delle novità introdotte. Complimenti per l'iniziativa, e come dice la pagina stessa: tenete d'occhio la pagina per gli ulteriori 51 (o 54?) esempi che hanno già promesso.
di Enrico SabbadinUn mio collega mi ha segnalato questo link interessante
http://troels.arvin.dk/db/rdbms/
Sono raccolte le differenze nella sintassi SQL tra i diversi database, molto utile per chi deve fare veramente programmazione multi database.
Questo mi stimola a "pubblicare" alcune considerazioni che mi sono "tenuto" da un po' di giorni.
Tutta la pubblicità data al nuovo modello a provider in .NET 2.0 mi fa un po' sorridere siccome raggiungere una programmazione indipendente dal tipo è essenzialmente facile da ottenere (io per esempio ho fatto un mio Smart Data Provider che incapsula i veri oggetti Connessione, comando, parametro, etc ..) ..
Poi ho però sentito parlare ben poco dei veri problemi che ti aspettano quando provi a fare una programmazione indipendente dal datatabase (c'è qualcos'altro che mi è sfuggito in ADO.NET 2.0 ? , se si segnalatemelo) :
Innanzitutto ci sono i parametri (che dovete usare per evitare SQL injection e i mal di pancia per le diverse rappresentazioni del tipo data se volete passarli come stringa). Ognuno ha i suoi modi di definirli
a) OLEDB e ODBC posizionali con ? c) SQLClient @parametro d) Oracle :Parametro e) MySQl .NET Connector :Parametro nel comando e Parametro come nome f) PostgreSQL provider .NET Npgsql :Parametro nel comando e Parametro come nome
Poi c'è il tipo del parametro : Esiste la proprietà DBType comune a tutti i parametri e XXXType specifica di un provider particolare. Per una programmazione indipendente dal provider uno deve usare la proprietà DBType. Ogni implementazione di XXXParameter cerca di mappare sul tipo specifico XXXType. A volte va bene a volte no , ed in tal caso dovete forzare un XXXType specifico per far funzionare le cose (ed allora se la cosa non è incapsulata addio programmazione indipendente dal database). Per esempio su oracle un parametro che atterra su un campo clob (equivalente ad un text di SQL) deve essere impostato come OracleType.CLOB perchè impostare DbType = DbType.String fa trattare il campo come unicode sballando tutto. Access invece a riguardo dei datetime deve ricevere esplicitamente un OLEDBType.Date perchè impostare il DBType a DateTime mappa OLEDBType a TimeStamp (che provoca errore) .. e posso andare avanti abbastanza a lungo :)
Ed infine veniamo alla sintassi SQL ed i suoi vari dialetti .. li puo' servire il link che metto in testa al post .. Giusto due cose : dimenticatevei della TOP e mettete le parentesi quando fate join su una join , infine la clausola AS : per sql server è opzionale sia sui campi che sulle tabelle, per access è obbligatoria sui campi e opzionale sulle tabelle, per oracle è opzionale sui campi e proibita sulle tabelle .. Quindi usate AS sempre per ridefinire il nome dei campi e mai per ridefinire il nome di tabelle. Per altre differenze in cui non si riesce a trovare "un punto comune", vi consiglio di avere un punto centralizzato (tipo un oggetto SmartCommand :) ) che con regular expression cerca di aggiustarvi un po' le cose :) Quando poi la sintassi è completamente diversa (per esempio quando dovete restituire le prime n righe di una query : su oracle usate ROW_NUMBER() , su SQLServer TOP , etc ..) inventatevi qualche altro meccanismo, per esempio scrivete dei data objects specifici del database o un meccanismo di pick della stringa template SQL giusta da un repository in base al database target (funziona bene finchè avete lo stesso numero di parametri in tutti i template).
Senza un qualcosa che vi medi nell'impostazione dei parametri e del commandtext potete quindi scordarvi la programmazione db independent. Nel mio SmartaData Provider attualmente disponibile intercetto i commandtext mentre non c'è mediazione sui parametri. Nella nuova versione che spero di rendere disponibile entro agosto ci sarà un oggetto SmartParameter che medierà tutte le problematiche sui parametri che vi ho descritto qua sopra... Esiste poi già un meccanismo di redirecting nella creazione di DataObjects in base al database target (impostabile attraverso file XML di configurazione). Non c'è ancora un meccanismo di pick di stringhe SQL template da repository, forse nella prossima versione (in ogni modo nei SabbaSoft application blocks c'è già tutta l'infrastruttura di configurazione disponibile per potervela scrivere voi se ne avete bisogno).
bye
|
|
Feed di Blog2theMax
Cerca nel blog
Archivio
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 28 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Categorie
Powered by: newtelligence dasBlog 1.7.5016.2
|