lunedì 8 marzo 2010
#
Probabilmente è il post dell’idiota del mese, però dato il tempo che ci ho perso…
Ho appena installato Visual Studio 2010 perchè devo preparare le sessioni per la conferenza del 19 marzo prossimo a Pordenone, quando lavoro in un ambiente di sviluppo io devo stare a mio agio, avere i miei quattro plugin funzionanti indipendentemente dal fatto che stia scrivendo codice serio oppure codice demo (non che non sia serio anche quello…) perciò ho deciso di vedere se potevo trasportare i miei bellissimi plugin da una versione all’altra.
Su Visual studio 2008 ho ancora installato Dxcore 3.0.5 (Se funziona perché aggiornarlo?) ma su 2010 non funziona, pertanto ho dovuto installare la versione 10.0.0 beta. Fin qui non ci sono problemi, l’installazione è semplice. Però, dopo l’installazione sono andata a cercare la familiare cartella bin\Plugins ove copiare i vecchi plugin e non c’era più. Ho provato a generarla ma ovviamente non funziona così dopo alcuni minuti di scoramento ho iniziato le ricerche, non è stato facile perché sia con Bing che con Google ho trovato molto poco, però ho trovato quasi per caso il sito dei plugin di community che si trova qui:
http://code.google.com/p/dxcorecommunityplugins/ e dopo varie ricerche sullo stesso in un link secondario ho trovato quello che mi serviva:
Dalla versione 9.1.1 la cartella dei plugin è: C:\Program Files\DevExpress 2009.1\IDETools\Community\Plugins\ ovvero: Cartella di installazione\IDETools\Community\Plugins\.
Dalla versione 9.1.3 la cartella dei plugin è: MyDocuments\DevExpress\IDE Tools\Community\PlugIns ovvero:
Documenti dell’utente corrente\DevExpress\IDE Tools\Community\PlugIns
Per evitare che come me perdiate due ore per installare un plugin, ho deciso di postare il tutto qui, sperando che l’indicizzazione possa aiutarvi a trovare le cose più in fretta. Per inciso, i due plugin più importanti, ovvero Commenter e ClassCleaner, funzionano perfettamente. I plugin di tipo visuale (Evidenziatore del TODO e visualizzatore del Color) non funzionano nel nuovo editor, probabilmente perché i comandi dell’ide WPF sono diversi.
Nel mentre facevo le mie ricerche, sono incappata anche in un altro plugin di Refactoring che trovo molto interessante perché permette di configurare il layout delle proprie classi in modo personalizzato e da delle funzioni molto carine di riorganizzazione del codice, se volete potete provarlo su 2008, per 2010 c’è un workaround ma non funziona ancora perfettamente; si chiama Regionerate e lo trovate qui: http://www.rauchy.net/regionerate/ ovviamente funziona solo in C# mi spiace per gli amici di VB ma sembra che i produttori di plugin per Visual Studio vi trascurino molto…
giovedì 4 marzo 2010
#
Fra le novità più importanti di SQL Server 2008, una è certamente la possibilità di utilizzare il file System per la memorizzazione dei dati di tipo BLOB. Pertanto se lavorate con Immagini, File binari ed altro che dovete memorizzare in un database SQL Server, è possibile utilizzare questa funzionalità, importante ad esempio per chi usa SQL Express, perché in questo modo, la zona per i BLOB non occupa spazio utile nei files del database, che sono limitati se non vado errata a 4GB.
Come si attiva questa funzionalità:
- Primo passo, attivare la funzionalità a livello di protocolli del server, dal Menu Programmi > Microsoft SQL Server 2008 > Configuration Tools – lanciare SQL Server Configuration Manager (Icona con la cassetta attrezzi rossa).
- Selezionare SQL Server Services nella treeview a sinistra
- Sulla finestra di destra selezionare SQL Server (MSSQLSERVER) per l’istanza di default oppure l’istanza nominata.
- Tasto destro proprietà
- Sulla finestra c’è una serie di Tab, scegliere FILESTREAM attivare l’accesso per Transact SQL ed eventualmente l’accesso via API e l’accesso da client remoti ( non sono certa sia obbligatorio settarlo se si usa il TSQL ma non ho ancora effettuato test in merito.)
- Secondo passo, in SQL Management Studio, collegarsi al server con diritti da Sysadmin, sul Server SQL tasto destro Properties, Selezionare Advanced nella lista sulla sinistra e sulla prima riga della property grid a destra, attivare l’opzione Filestream In modalità Transact SQL o Full Access.
- Creare un database che vi permetta di memorizzare dati FILESTREAM:
USE [master]
GO
/****** Object: Database [Paperinik] Script Date: 03/04/2010 18:09:07 ******/
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'Paperinik')
BEGIN
CREATE DATABASE [Paperinik] ON PRIMARY
( NAME = N'Paperinik', FILENAME = N'C:\SQL.DIR\Data\Test\Paperinik.mdf' ,
SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB ),
FILEGROUP [Binari] CONTAINS FILESTREAM DEFAULT
( NAME = N'Paperinik_Bin', FILENAME = N'c:\sql.dir\data\test\PaperinikFS\paperinik_bin' )
LOG ON
( NAME = N'Paperinik_log', FILENAME = N'C:\SQL.DIR\Data\Test\Paperinik_log.ldf' ,
SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
END
Questo esempio, fatto con il mio database preferito :D crea il database vero e proprio con il file Paperinik.mdf come master data file ed il file Paperinik_log.ldf come file di log. Aggiungo poi un Filegroup che chiamo Binari che definisce la zona relativa ai FILESTREAM, indicando la cartella dove memorizzare i dati, nel mio caso, c:\sql.dir\data\test\PaperinikFS\
- Creare una tabella che contenga un campo Varbinary(MAX) con l’opzione FILESTREAM
USE [Paperinik]
GO
CREATE TABLE [dbo].[TbFiles](
[IDFile] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[IDFileSerial] [int] NOT NULL,
[DDFile] [nvarchar](255) NULL,
[FileData] [varbinary](max) FILESTREAM NULL,
CONSTRAINT [PK_TbFiles] PRIMARY KEY NONCLUSTERED
(
[IDFile] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] FILESTREAM_ON [Binari]
GO
USE [Paperinik]
/****** Object: Index [UQ_TbFileSerial] Script Date: 03/04/2010 17:23:33 ******/
CREATE UNIQUE CLUSTERED INDEX [UQ_TbFileSerial] ON [dbo].[TbFiles]
(
[IDFileSerial] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF,
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] FILESTREAM_ON [Binari]
GO
In questo caso, ho creato una tabella per memorizzare dei file, anche se poi nell'esempio memorizzeremo qualcosa di più facile, per le cose più complesse farò ulteriori test in seguito. Per poter contenere un campo di tipo FILESTREAM è indispensabile che la tabella contenga un ID univoco di tipo GUID (Il campo IDFile) ho anche generato un campo univoco con codice numerico che può divenire un metodo “potabile” per cercare i record, e gli ho quindi assegnato un indice univoco clustered.
- Ora non ci resta che Inserire dei dati di test, questi dati possono essere creati in modo semplice usando uno script T-SQL di inserimento:
INSERT INTO [Paperinik].[dbo].[TbFiles]
([IDFile]
,[IDFileSerial]
,[DDFile]
,[FileData])
VALUES
(NEWID()
,1
,'Nullo'
,null)
GO
INSERT INTO [Paperinik].[dbo].[TbFiles]
([IDFile]
,[IDFileSerial]
,[DDFile]
,[FileData])
VALUES
(NEWID()
,2
,'Vuoto'
,cast('' as varbinary(max)))
GO
INSERT INTO [Paperinik].[dbo].[TbFiles]
([IDFile]
,[IDFileSerial]
,[DDFile]
,[FileData])
VALUES
(NEWID()
,3
,'Stringa'
,cast('Questa stringa è binaria' as varbinary(max)))
GO
INSERT INTO [Paperinik].[dbo].[TbFiles]
([IDFile]
,[IDFileSerial]
,[DDFile]
,[FileData])
VALUES
(NEWID()
,4
,'La quarta'
,cast('Questa riga contiene questa stringa' as varbinary(max)))
GO
In questo caso, ho generato quattro righe, contenenti un campo binario nullo, un fantomatico file di testo vuoto, e due files di testo con una stringa al loro interno.
- Cosa troveremo nel nostro file system dopo queste operazioni?
Verrà generato un sistema di cartelle, sotto alla cartella ROOT c:\sql.dir\data\test\PaperinikFS\ i cui nomi sono dei GUID, che saranno utilizzate per memorizzare i dati relativi ai nostri VarBinary (MAX) di tipo filestream. Se curiosate nelle sottocartelle, troverete dei file senza alcuna estensione con nomi in stile nnnnnnnn-nnnnnnnn-nnnn che aperti con notepad, visualizzeranno il contenuto del campo varbinary. Non ho ancora provato a memorizzare nella tabella un file binario e vedere cosa troverò, lascio ad un post futuro il compito di rivelarlo.
A che conclusioni ci porta questo piccolo esperimento? Se abbiamo bisogno di memorizzare dati non strutturati in un database, questa può essere una buona soluzione, perché SQL Server fornisce comunque le transazioni, il log delle modifiche e variazioni, con un purge che può essere configurato, la possibilità di attivare l’indicizzazione. La possibilità di fare il backup del filesystem che costruisce come qualsiasi altro filesystem. Ci toglie dall’imbarazzo per il problema del limite a 4GB dei database di SQL Express, ma attenzione, il formato del filesystem non è comunque direttamente accessibile dall’esterno, se si perdono i database, recuperare fisicamente i files binari è possibile, ma sapere cos’è ciascuno dei files recuperati NON è una cosa semplice.
Chi avesse bisogno di indicizzare su database dati destrutturati posti su file system che vuole comunque vedere e poter accedere anche usando il file system stesso, non deve usare questa funzionalità, ma costruire un gestore manuale del file system e memorizzarsi sul database i percorsi dei files che gestisce.
venerdì 29 gennaio 2010
#
Scenario: C’è un problema su SQL 2005 e SQL 2008 dovuto alle funzionalità di compatibilità con SQL 2000. In SQL 2000 infatti era possibile inserire in una vista una clausola ORDER BY inserendo semplicemente una SELECT TOP 100 PERCENT nella sua definizione.
Ovviamente, essendo una cosa possibile se pure sconsigliata tutti noi l’abbiamo usata in modo massiccio :D, dopo l’SP2 o SP3 in SQL 2005 questa funzionalità disponibile per i database in modalità compatibilità SQL 2000, cessa di funzionare.
L’ho scoperto su un sistema di produzione presso un cliente, dove una piccola funzione sviluppata in VBA su Access, e usata dal 2001 ha improvvisamente cessato di funzionare lo scorso 15 ottobre.
C’è una Hot Fix (fatta dopo SP2 ma non inclusa in SP3) che risolve il problema disponibile qui, ma ovviamente è opportuno trovare tutti i posti in cui si usano queste Order By e provvedere a ordinare le Query dopo la vista anziché dentro la vista.
Dopo aver modificato il VBA Access per risolvere il problema contingente la domanda è stata:
Ma quante viste con una ORDER BY ho ancora in questo vecchio database? (Vecchio ma ovviamente pesantemente usato in quanto è il DB del Gestionale).
Aprire a mano 96 viste è una operazione da suicidio, quindi ho cercato in internet se c’è un modo per cercare dentro l’SQL di definizione degli oggetti di un database ed ho trovato in un forum su stack overflow quello che inserisco qui sotto, in caso serva a qualcun’altro:
SELECT
v.name,
m.definition
FROM
sys.views v
INNER JOIN
sys.sql_modules m ON v.object_ID = m.object_id
WHERE m.definition LIKE '%TOP%'
Con questo script posso cercare tutte le definizioni di viste che contengono la parola TOP, ma è facilmente modificabile per cercare qualsiasi stringa in qualsiasi porzione di codice SQL relativo alla definizione di un oggetto in un database.
Grazie a Mark S. per averlo condiviso.
P.s. le viste da modificare sono 72 [sigh! :-((( ]
giovedì 31 dicembre 2009
#
Se come me, lavorate con Analisti creativi, succede spesso di dover modificare i database per aggiungere tabelle, cancellare tabelle, modificare tabelle, pertanto visto che è buona norma che se eseguite due volte uno script SQL ci sia un minimo di controlli affinchè non vadano in errore scrivo qui la sintassi base di alcuni dei più comuni controlli di esistenza per gli oggetti database, così anche io quando ne ho bisogno vengo qui e faccio copia ed incolla :P
IF NOT EXISTS (
SELECT 1 FROM sysobjects WHERE xtype='u'
AND name='MyTable')
BEGIN
CREATE TABLE Mytable …;
END
Questo primo pezzettino può anche essere usato togliendo il not per fare un Drop Table.
IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id =
OBJECT_ID('[FK_MyForeignKeyConstraintName]')
AND parent_object_id = OBJECT_ID('[MyTable]'))
ALTER TABLE [MyTable] DROP CONSTRAINT [FK_MyForeignKeyConstraintName]
Questo pezzettino va usato quando abbiamo bisogno di cancellare una tabella che ha all’interno delle relazioni, prima si devono cancellare le relazioni, poi la tabella oppure da errore.
IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('MyTable') AND name='MyColumnName')
ALTER TABLE MyTable ADD MyColumnName int NULL;
Questo controllo è opportuno sugli script che aggiungono colonne, ma allo stesso modo, senza NOT può essere usato per cancellare colonne.
mercoledì 30 dicembre 2009
#
Oggi sto lavorando su una piccola applicazione che, fra le altre cose, ha il compito di notificare ad alcuni utenti l’arrivo di dati da un server ftp.
L’applicazione ascolta la cartella di arrivo del server ftp in modo diretto tramite un file system watcher e quando un file viene ricevuto, lo sposta nella giusta sede e notifica agli utenti l’arrivo dei nuovi dati.
La notifica viene spedita via e-mail. Spedire un e-mail usando .NET è molto facile, ma cercando uno snippet che lo facesse sul web non ho trovato alcun esempio semplice, pertanto ne ho scritto uno qui in modo che se a qualcuno serve è pronto.
//Metodo di trasmissione email
public void Send( string pUser, string pPassword )
{
//predispone l’inidirzzo del mittente
MailAddress from = new MailAddress("noreply@mydomain.com");
//predispone l’indirizzo destinatario, se ne avete più di uno basta aggiungerlo
//alle collezioni To, CC or BCC della classe MailMessage
MailAddress to = new MailAddress("recipientuser@hisdomain.com");
//Creo il messaggio
MailMessage msg = new MailMessage();
//Metto il mittente negli indirizzi from, mittente, risposta
msg.From = from;
msg.Sender = from;
msg.ReplyTo = from;
//Metto il destinatario nella collezione
msg.To.Add(to);
//predispongo l’oggetto
msg.Subject = "Subject of my message";
//Do al messaggio una priorità
msg.Priority = MailPriority.Normal;
//indico se il messaggio è un testo o un HTML
msg.IsBodyHtml = false;
//Scrivo il corpo del messaggio
msg.Body = "Simple text message body";
//Aggiungo un attachment di esempio
if (File.Exists("c:\\myattachment.xml")
{
msg.Attachments.Add(new Attachment("c:\\myattachment.xml"));
}
//Genero il client SMTP indicando l’indirizzo del server
SmtpClient smtp = new SmtpClient("mail.mydomain.local");
//indico il metodo di trasmissione
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
//Qui è possibile anche modificare la porta se necessario
//Se il server richiede le credenziali, le indichiamo qui
if (pUser != null && pUser.Trim().Length > 0)
{
smtp.UseDefaultCredentials = false;
smtp.Credentials = new NetworkCredential(pUser, pPassword);
}
//E spediamo il messaggio
smtp.Send(msg);
}
Se il messaggio che dovete spedire è in HTML basta cambiare 2 righe:
//Indicate if it is an html or text message
msg.IsBodyHtml = true;
//Set the message body
msg.Body = "<html><body><p>Simple HTML message body.</p></body></html>";
è ovvio che tutte le stringhe della funzione di esempio le sostituirete con opportuni parametri e magari i dati del server li metterete nei setting applicativi così da creare una applicazione più flessibile. Buon lavoro.
Technorati Tag:
C#,
E-mail,
SMTP
sabato 28 novembre 2009
#
Un collega che sta studiando forsennatamente .Net, SQL, C# per poter costruire applicazioni gestionali professionali nel 21° secolo, vedendo un mio database mi ha fatto la seguente domanda:
Perché trovo l’utente MySqlUser nella Security del server, e poi lo trovo nel Database? E Perché mi dici di aver dato i permessi ad un ruolo creato sul database (db_MyRole) invece di darli all’utente?
Come funziona la sicurezza di SQL Server,
Per poter accedere a SQL Server un utente o una applicazione necessita di conoscere uno UserName ed una Password oppure l’utente di windows con cui si logga deve essere mappato all’interno di SQL Server o deve appartenere ad un gruppo di windows mappato all’interno di SQl Server cosa che si può fare nella sezione Security del server usando SQL Management Studio, oppure lanciando uno di questi due comandi:
CREATE LOGIN [MyUser] WITH PASSWORD=N'MyPassword'
, DEFAULT_DATABASE=[master]
, DEFAULT_LANGUAGE=[us_english]
, CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
CREATE LOGIN [MyWindowsUserOrGroup]
FROM WINDOWS WITH DEFAULT_DATABASE=[master]
, DEFAULT_LANGUAGE=[us_english]
Ma non basta, infatti con questo login, SQL Server permetterà ad un utente di aprire una connessione, ma non darà loro modo di accedere ad alcun dato all’interno di un database.
Per accedere ad un database, i Login (SQL o Windows che siano) devono essere per prima cosa mappati sul database usando SQL Management Studio. Altrimenti con il seguente codice SQL.
CREATE USER [MyUser]
FOR LOGIN [MyUser] WITH DEFAULT_SCHEMA=[dbo]
CREATE USER [MyWindowsUserOrGroup]
FOR LOGIN [MyWindowsUserOrGroup] WITH DEFAULT_SCHEMA=[dbo]
Adesso gli utenti possono accedere al server e possono accedere al database, però non abbiamo dato loro alcun permesso sugli oggetti contenuti nel database, pertanto non possono vedere nulla. Detto questo abbiamo due strade per farlo, assegnare i permessi direttamente all’utente oppure, con maggior furbizia possiamo costruire un ruolo a cui assegnare i permessi e poi assegnare al nostro utente il ruolo. Perché un ruolo? Perché il ruolo lo possiamo assegnare a più utenti quando necessario senza dover rifare tutta la mappatura dei permessi.
USE [MyDatabase]
GO
CREATE ROLE [MyRole] AUTHORIZATION [sa]
GO
use [MyDatabase]
GO
GRANT SELECT ON [dbo].[MyTable1] TO [MyRole]
GO
use [MyDatabase]
GO
GRANT EXECUTE ON [dbo].[MySpSelect1] TO [MyRole]
GO
use [MyDatabase]
GO
GRANT EXECUTE ON [dbo].[MySpSelect2] TO [MyRole]
GO
use [MyDatabase]
GO
GRANT SELECT ON [dbo].[MyTable2] TO [MyRole]
GO
A questo punto diamo ai Login mappati il ruolo e abbiamo finalmente raggiunto il traguardo. Lo possiamo sempre fare da SQL Management Studio oppure con il codice seguente:
USE [FyRepository]
GO
EXEC sp_addrolemember N'MyRole', N'Myuser'
GO
Ricapitoliamo:
Per poter accedere ai dati di un database un’utente o una applicazione deve avere modo di connettersi tramite un Login, il Login deve essere mappato su un Database e deve essere assegnato a Uno o più ruoli che gli forniscono i permessi sugli oggetti del database che poi utilizzerà per compiere il loro lavoro.
giovedì 26 novembre 2009
#
Oggi, ho trovato un pezzetto di SQL che reputo molto utile, al solito lo condivido nella speranza di dare una mano ;).
Lo scenario è il seguente:
Ho un server remoto da cui devo importare una serie di tabelle sul mio SQL Server locale per successive elaborazioni. I due server sono connessi in VPN. La connessione è buona ma non posso tagliare la banda facendo una lunga serie di elaborazioni dati direttamente sul server remoto. Perciò, per prima cosa ho creato un linked server fra il mio server e il server remoto. In questo modo posso eseguire delle query sul database remoto di cui ho bisogno. Poi, per trasferire la tabella che mi serviva, ho scritto questo codice
USE MyNewLocalDb
SELECT FIELD1, FIELD2, FIELD3 .... FIELDn INTO dbo.MynewTable FROM
[MyRemoteServer].[MyRemoteDb].[Dbo].[MyremoteTable]
In questo caso la mia query era molto semplice, perché mi serviva una sola tabella, ma se avessi avuto bisogno di fare una serie di join non ci sarebbe stato alcun problema, basta scriverle nella clausola from. La tabella creata è molto grezza, ma vi si può poi aggiungere la PK, gli indici e qualsiasi altra cosa ci serva per l’elaborazione.
Ovviamente, per fare tutto questo dovete essere dotati dei diritti sufficienti, ovvero essere quantomeno DB_Owner sul database di destinazione e DB_Datareader sulla tabella di origine.
venerdì 16 ottobre 2009
#
Iniziando a vedere come gira Team Foundation Server è ovvio che si comincia a fare dei pasticci creando progetti e vedendo che cosa succede, ergo, poter ripulire i danni fatti è essenziale. Dato che al solito mandare a memoria comnadi console è un po’ ostico, ecco la pagina della MSDN che ci insegna come fare. Per inciso, si può fare esclusivamente da linea di comando quindi i fanatici delle GUI si devono rassegnare.
MSDN Cancellare un Team Foundation Server Project
Ulteriore annotazione per i beginners come me: Non si possono rinominare i progetti di TFS pertanto prima di creare un progetto controllate che cosa state scrivendo ;o)
venerdì 21 agosto 2009
#
Annoto questo pezzetto di codice per tenerne nota, è la versione .NET (C#) della funzionalità che in VB6 o VBA permetteva di creare un oggetto Word.Application che in caso il suddetto fosse aperto non generasse una nuova istanza ma usasse quella presente.
In questo modo, se ci sono documenti aperti li troveremo nella collection corrispondente, quindi potremo agganciarli e usarli Il codice sostituisce la famigerata GetObject non tipizzata ed è il seguente:
Word.Application myWord = null;
try
{
myWord = System.Runtime.InteropServices.Marshal.GetActiveObject(
"Word.Application") as Word.Application;
}
catch (System.Runtime.InteropServices.COMException ex)
{
if (ex.Message.Contains("Operation unavailable") || ex.ErrorCode == -2147221021)
{
myWord = new Word.ApplicationClass();
}
else
{
throw ex;
}
}
Il codice su scritto assume che sia stata creata la seguente clausola using:
using Word = Microsoft.Office.Interop.Word;
La try/catch viene utilizzata perché se Word non è aperto otteniamo l’eccezione su scritta, ho usato una OR per testare 2 delle cose che ci sono nei dati dell’exception perché non sono certa sia usato lo stesso codice o la descrizione non sia tradotta in base alla locale. Se avete un metodo più certo per testare una Com Exception parlate pure ;)
P.s. Lo stesso metodo può essere utilizzato per agganciare Excel, Outlook o qualsiasi altra applicazione via Interop. Per Excel cambia la stringa che diviene "Excel.Application" presumo che per Outlook sia "Outlook.Application" e così via.
giovedì 13 agosto 2009
#
Usualmente lo script per rinominare una tabella in SQL Server è composto da una Alter Table abbastanza complessa, mentre se usiamo il Designer di SQL Management Studio, possiamo rinominare manualmente una colonna senza problemi.
Il designer ha anche un bottoncino che è attivo solo prima che noi premiamo il tasto Salva dopo aver fatto le modifiche alla nostra tabella. Tale bottoncino si chiama Generate Change Script ed è il primo della seconda toolbar quando siamo dentro al designer.
Premendo questo bottoncino, ci viene generato uno script SQL con tutte le modifiche che abbiamo effettuato sul database. E’ ovvio che è indispensabile premerlo prima di salvare perché il tasto Salva esegue lo script e perde le modifiche effettuate.
Detto questo, per caso stamattina l’ho usato dopo aver rinominato una colonna ed ho ottenuto questo:
USE [DbName]
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
EXECUTE sp_rename N'dbo.Mytable.MyField', N'Tmp_MyNewField_1', 'COLUMN'
GO
EXECUTE sp_rename N'dbo.MyTable.Tmp_MyNewField_1', N'MyNewField', 'COLUMN'
GO
COMMIT
Scoprendo quindi che esiste una stored procedure di sistema che si occupa di rinominare la colonna, vedo da quanto possiamo notare che fa 2 passaggi, probabilmente per sicurezza, è una cosa comoda da usare, pertanto la annoto qui come sempre per non dimenticarmene.
Ricordo ovviamente a chi usa questo tipo di script che rinominando una colonna vanno verificate tutte le stored procedure e le viste che la utilizzassero per evitare errori inoltre vanno verificati gli script manuali sparsi per i nostri programmi.
giovedì 9 luglio 2009
#
Intitolare un post col nome di una Enumerazione è strano, però credo sia il modo migliore perché la cosa sia indicizzata dai motori di ricerca. Questo post scaturisce dalla mia compassione verso coloro i quali, come me oggi dovranno in futuro lavorare con Word e le librerie Interop di .NET. Perché questa enumerazione è piuttosto importante, infatti permette di spostare la selezione e quindi il Cursore Virtuale che pilotiamo da codice con le Interop nelle varie “Viste” che compongono un documento.
MSDN contiene l’enumerazione nelle sue pagine, (qui) esattamente come contiene tutto lo scibile relativo a qualsiasi libreria, classe, oggetto e chi più ne ha più ne metta sviluppato nel tempo dai programmatori Microsoft, però conoscendo bene i programmatori, (e io alzo la mano per prima) e la loro poca predisposizione alla documentazione la descrizione delle opzioni dell’enumerazione è ovviamente una semplice riscrittura in lingua inglese dei loro nomi, purtroppo non sufficiente a rispondere alle domande che uno si pone nel momento in cui deve usarla davvero.
Pertanto, avendo scritto una applicazione di test proprio per svelare l’arcano, condivido le informazioni rilevate sperando che chi ha bisogno di pilotare la scrittura di dati su un documento word da codice .Net trovi questo post e possa trarne beneficio.
Un documento Word può essere generato con quattro tipologie di Intestazioni e piè pagina, ovvero:
- Diversi per la prima pagina.
- Diversi per la prima pagina e per le pagine pari e dispari.
- Diversi per le pagine pari e dispari
- Tutti uguali
Per accedere al contenuto delle intestazioni e piè pagina, è necessario fare qualcosa di simile a questo:
public Word.Range SelectWholeViewPlace(Word.WdSeekView pSeekPlace)
{
try
{
Doc.Activate();
Word.Range rng = null;
try
{
Doc.Application.ActiveWindow.View.SeekView = pSeekPlace;
object units = Word.WdUnits.wdStory;
object extend = Word.WdMovementType.wdMove;
Doc.Application.Selection.HomeKey(ref units, ref extend);
extend = Word.WdMovementType.wdExtend;
Doc.Application.Selection.EndKey(ref units, ref extend);
rng = Doc.Application.Selection.Range;
}
catch (Exception)
{
rng = null;
}
return (rng);
}
catch (Exception ex)
{
EventLogger.SendMsg(mClassName,
System.Reflection.MethodBase.GetCurrentMethod(), ex, MessageType.Error);
throw new ApplicationException(GlobalConstants.TXT_SPACE + mClassName + GlobalConstants.TXT_DOT
+ System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
}
}
Il metodo qui sopra fa un seleziona tutto in una delle “Viste” di un documento word, e se la vista non esiste (quindi ad esempio se il documento è in modalità 4 e non ci sono intestazioni delle pagine pari, dispari o della prima pagina) ritorna un oggetto Range nullo.
Ma quale opzione usare e in quali casi? Ecco le descrizioni complete:
| Valore |
Significato |
| wdSeekCurrentPageFooter |
Attiva la Vista sul Footer (piè pagina) della pagina corrente;
per pagina corrente si intende la pagina su cui il cursore è posizionato, pertanto se siamo nel caso 1 e abbiamo il documento aperto e il cursore sulla prima pagina, ci verrà selezionato il footer della prima pagina, se siamo nel caso 2 e siamo su una pagina Dispari diversa dalla prima, ci verrà selezionato il piè pagina delle pagine Dispari e così via.
Quindi questa opzione ha un uso difficile in un operazione pilotata da Interop, perché a priori non sappiamo dove finiremo. |
| wdSeekCurrentPageHeader |
Attiva la Vista sull’Header (intestazione) della pagina corrente; vedi riga precedente per le modalità funzionali. |
| wdSeekEndnotes |
Attiva la vista sulle Note di fine documento. (quando esistono) |
| wdSeekEvenPagesFooter |
Attiva la vista sul Footer delle pagine Pari, quindi funziona nei casi 2 e 3 e non esiste nei casi 1 e 4. |
| wdSeekEvenPagesHeader |
Attiva la vista sull’Header delle pagine Pari; vedi riga precedente per le modalità funzionali. |
| wdSeekFirstPageFooter |
Attiva la vista sul Footer della prima pagina, quindi funziona nei casi 1 e 2 e non esiste nei casi 3 e 4. |
| wdSeekFirstPageHeader |
Attiva la vista sull’Header della prima pagina; vedi riga precedente per le modalità funzionali. |
| wdSeekFootnotes |
Attiva la vista sulle note a piè pagina. (quando esistono) |
| wdSeekMainDocument |
Attiva la vista sul testo del documento. |
| wdSeekPrimaryFooter |
Attiva la vista sul footer principale, questa opzione come l’opzione CurrentPage assume significato diverso in base ai casi in cui ci troviamo:
- Attiva il footer delle pagine diverse dalla prima.
- Attiva il footer delle pagine dispari
- Attiva il footer delle pagine dispari
- Attiva il footer primario (e unico disponibile).
|
| wdSeekPrimaryHeader |
Attiva la vista sull’Header principale; vedi riga precedente per le modalità funzionali. |
Buon Interop a tutti.
giovedì 25 giugno 2009
#
Antefatto:
All'interno della mia azienda e del mio gruppo di lavoro, esiste un nucleo di DLL di uso generico (General Purpouse - un po' come Cillit Bang) che contengono classi e oggetti usati in tutti i tipi di progetto.
Per rendere agli sviluppatori la vita più facile, tutte queste librerie sono firmate e pubblicate in GAC sulle macchine di sviluppo.
In produzione invece, ovvero quando installate presso clienti, nessuna delle nostre DLL salvo necessità molto specifiche viene mai installata in GAC.
Fatto:
Oggi, il mio collega stava lavorando su una infima applicazioncina WEB (infima perché ha 2 e dico 2 pagine di cui una è la pagina di login).
Preparato il tutto ha deciso di provarla sul nostro server WEB interno che ovviamente non è un PC di sviluppo e non ha le DLL in GAC. Per fare questo ha usato la procedura di Publish del sito web per creare la versione compilata. Lo ha installato e OOPS! mancano tutte le librerie che erano in GAC...
Abbiamo fatto una ricerca sul web e trovato su un forum questa bellissima risposta.
Hi,
This is by design in ASP.NET 2.0.
If you deploy an application that contains a reference to a custom component that is registered in the GAC, the component will not be deployed with the application. In previous versions of Visual Studio.NET, you could set the Copy Local property for a reference, which ensured that the assembly would be deployed. In ASP.NET 2.0, to deploy any assembly with your application, you must manually add the assembly to the application's Bin folder.
For more information, see http://msdn.microsoft.com/en-us/library/hsy826az(VS.80).aspx
Tesi:
Quando si pubblica un sito che usa e comprende delle librerie che si trovano in GAC sulla macchina di sviluppo si deve copiarle tutte a manina nella cartella BIN (quindi se si aggiornano bisogna ricordarsi di ricopiarle ogni volta). Oppure, bisogna lavorare sul server di produzione, oppure bisogna pubblicare sul server destinazione le DLL nella sua propria GAC. Lo stesso ovviamente vale per tutte le librerie non appartenenti al framework ma prodotte da terze parti che utilizziate nella vostra applicazione.
Non so voi, ma io usualmente estirpo la cartella BIN di un progetto molto, molto spesso quando faccio pulizia e rigenero una soluzione :D.
A questo punto è opportuno e necessario dotarsi di un BATCH che ad ogni compilazione del sito e/o ad ogni generazione della versione di pubblicazione si occupi di ricopiarsi tutte le DLL dalle rispettive cartelle alla cartella di pubblicazione.
Purtroppo, i progetti web essendo diversi da tutti i progetti normali non sono dotati del Post Build Event.
Leggendo l'articolo, c'è una certa logica nella motivazione di questa feature, non lo metto in dubbio, però per quanto mi riguarda, tutto questo risolve un problema ma ne crea invece un'altro, ovvero, io rischio di pubblicare un website o creare un pacchetto di installazione con delle DLL obsolete.
Workaround da noi adottato:
Aggiungere alla soluzione contenente il website una DLL contenente una classe DUMMY che sul PostBuildEvent esegue un batch che ricopia le DLL usate dal Website e contenute nella GAC sulla macchina di sviluppo all'interno della cartella BIN del website così che il Publish le trovi e noi siamo certi che sono quelle più aggiornate.
Chi avesse idee meno troglodite di questa, magari conoscendo meglio di noi ASP abbia pietà e suggerisca.
sabato 30 maggio 2009
#
Ogni tanto i nomi scelti dai programmatori Microsoft fanno pensare che qualcuno di loro sia originario delle nostre parti visto che “orca miseria” è una delle imprecazioni favorite qui da noi :D. In questo caso però l’oggetto con questo nome è piuttosto utile per osservare e modificare cosa c’è dentro ad un msi compilato.
ORCA non ha nulla a che vedere con ORCAS, è infatti un editor per la modifica dei files MSI che fa parte dell’ SDK dell’Installer di windows.
Ne ho scoperto l’esistenza per caso, cercando la soluzione ad un problema noioso, ovvero come generare uno shortcut nella cartella SendTo dell’utente installando una applicazione. Non è possibile farlo usando le opzioni per la generazione degli shortcut dei progetti di setup di Visual Studio.Net, questo perchè i soli shortcut generati da questi progetti sono quelli di tipo Advertised, che hanno una forma e struttura ben diversa da quelli normalmente generati quando usiamo il tasto destro su Windows (XP o Vista non cambia). E’ un problema noto dai tempi di 2003 fa parte di una wishlist da allora ma non è detto che sarà mai un desiderio realizzato.
Però, esiste un workaround fornito appunto da ORCA, infatti, se dopo aver generato un file msi vogliamo fare in modo che gli shortcut da esso generati non siano di tipo Advertised ma normali, possiamo intervenire aprendo il file msi usando ORCA (che potete scaricare qui). Posizionarci su Property usando la lista a sinistra, poi cliccare con il tasto destro sulla finestra a destra e aggiungere una property così fatta:
Nome: DISABLEADVTSHORTCUTS Valore: 1
In questo modo, gli shortcut vengono generati con la forma normale. Ovviamente l’operazione va fatta ogni volta che l’msi viene generato, cosa noiosa, però abbastanza semplice.
Tags: msi, Installer, Orca, Setup
martedì 14 aprile 2009
#
Un post anomalo in un blog tecnico, però in questi giorni sto preparando l'evento di Pordenone ed ho ritenuto cosa interessante cercare le aziende che si occupano di Consulenza Informatica in Friuli Venezia Giulia per inviare una e-mail di invito. Il luogo più consono mi sembrava fossero le Pagine Gialle elettroniche, ed infatti ne ho trovati più di 400 però, con mia sorpresa, di questi 400 forse 20 hanno listato il sito web ed una e-mail, gli altri listano solo indirizzo e a volte numero di telefono. Capisco che un'informazione come il link ad un sito web e una e-mail siano qualcosa che la registrazione sulle pagine gialle fa pagare, ma ragazzi, siamo informatici o caporali??? Detto questo, visto che internet è dotata di motori di ricerca, ho provato ad utilizzarli infilando le varie ragioni sociali come testo della ricerca. In parecchi casi ho trovato un sito ed una e-mail, ma in altrettanti ho trovato altre liste, Pagine Utili, Nuovi Clienti, Liste varie di aziende informatiche, però in nessuna c'erano informazioni di tipo informatico... Addirittura in qualche caso ho trovato un sito web senza alcun modo per contattare l'azienda salvo il numero di telefono. Non vorrete mica dirmi che avete paura dello spam? Che non siete neppure in grado di acquistare una casella con servizio antispam su libero??? Ed ora iniziate pure a raccogliere le pietre, ho già acceso la spada laser per intercettarle!!! ;o) ;O) ;O) ;O)
lunedì 30 marzo 2009
#
Se come me avete delle soluzioni che creano un certo numero di Components (da 30 a 200), la funzionalità per cui Visual Studio aggiunge automaticamente questi Tools alla Toolbox ad ogni ricompilazione, apertura della soluzione, modifica o aggiunta di una nuova classe, può risultare noiosa, prima di tutto perché durante questa operazione non ci sono cursori di attesa e sembra che Visual Studio sia Piantato, secondariamente perché difficilmente andrete a trascinare componenti in una soluzione di soli componenti se non per fare dei test, ma in questo caso credo siate sufficientemente cresciuti da poter scrivere una riga di codice a mano per creare il componente. Per togliere il fastidio basta eseguire la seguente modifica alle opzioni di Visual Studio: Tools > Options > Windows Forms Designer > ToolBox > AutoToolboxPopulate = false Al contrario, se per qualsiasi motivo questa funzione non vi venisse eseguita automaticamente e a voi serve, controllate che questo flag sia posto a True. Ricordo che le classi prese in considerazione da questa funzione sono quelle derivate da Control e Component.
Tags: VisualStudio Toolbox
venerdì 10 ottobre 2008
#
Quando lavoravo in C non c'era alcun problema a scrivere su un file binario un intero o un altro valore trasformandolo in un array di byte, bastava una cosa di questo tipo:
char bytes[5] = (char *)myinteger;
Ok, non è proprio preciso ma ci assomiglia :D scusate ma è un po' che non lo uso quindi a memoria non è facile. In .NET invece dove le tipizzazioni e il codice managed rendono a volte le cose complicate, visto che questo tipo di operazione non è quotidiana mi dimentico sempre come si fa, pertanto lo scrivo:
int val = 0x10008000;
byte[] bar = BitConverter.GetBytes(val);
File.WriteAllBytes("test.bin", bar);
Il BitConverter che forse sarebbe stato più simpatico si chiamasse ByteConverter, permette di convertire in un array di Byte vari tipi di dati.
L'utilità, come si può vedere dal codice qui sopra è quella di scrivere su un file binario il contenuto di una variabile. Ricordo che ci sono ovviamente strumenti più moderni e più adatti, la Serializzazione binaria ad esempio ma quando serve una cosa veloce, anche questo può aiutare.
Tags: Classi, Array, Byte
venerdì 22 agosto 2008
#
Una delle Feature più utili che SQL Server mette a disposizione dei DBA che scrivono Stored Procedure è la possibilità di utilizzare la funzione RAISERROR all'interno delle SP stesse generando codici e messaggi di errore personalizzati.
Prima di poter utilizzare questa funzionalità, si deve generare i nostri messaggi personalizzati sul Server SQL, per fare questo è necessario avere diritti da SysAdmin e utilizzare la seguente sintassi:
sp_addmessage @msgnum = 450001,
@severity = 10,
@msgtext = N'The mandatory parameter %s hasn''t been supplied';
@msgnum è un codice arbitrario, che è consigliabile sia maggiore di 50000 per non andare ad interferire o sostituire messaggi di sistema.
@severity è il livello di errore, più grave è l'errore maggiore il livello, 10 è un valore medio, sopra ai semplici warning e sotto agli errori di sistema gravi.
@msgtext è la stringa di errore che ovviamente deve contenere i doppi apici in sostituzione all'apice singolo (come nell'esempio hasn''t) e può se necessario contenere dei placeholder, i placeholder sono nello stile del linguaggio C, pertanto %s è una stringa, %d un intero, e poi non mi ricordo però sono certa che è scritto sui Books Online!
Il messaggio di esempio lo utilizzo nelle mie Stored Procedure per segnalare che manca un parametro obbligatorio, però potrebbero esservi molte occasioni in cui questi messaggi sono utilizzabili, per dare messaggi significativi allo sviluppatore quando utilizza una Stored procedure su un database se ad esempio sono due diverse persone che sviluppano la parte Dati e la parte Codice.
Per usare il messaggio sopra generato basta applicare il comando:
RAISERROR (450001, 10, 1, '@IDCluster');
Ed otterremo un errore con questo messaggio:
The Mandatory parameter @IDCluster hasn't been supplied
Per cancellare un eventuale messaggio di sistema che abbiamo generato possiamo utilizzare la Stored procedure sp_DropMessage:
sp_dropmessage @msgnum = 450001
Indagando un po' sui Books Online potrete trovare tutte le opzioni che si possono dare a queste 2 stored procedure per creare messaggi articolati e multilingue.
Tags: SQL, Database, Messaggi, SqlServer
In .NET quando si lavora su DB SQL Server con molte tabelle, il numero di Stored procedure sul database diviene piuttosto grande, se poi sfruttiamo appieno le possibilità di SQL Server aggiungendo User Defined Function ed ulteriori SP per fare elaborazioni, query complesse, Pivot e quant'altro, il numero diviene importante.
Se aggiungiamo nuove stored procedure al DB in produzione oppure se abbiamo l'abitudine di Cancellare e Rigenerare tali oggetti quando facciamo un aggiornamento software, gli utenti del nostro DB (A cui ovviamente avremo dato permessi minimi, come ad esempio db_datareader e db_datawriter) non avranno diritto di esecuzione sulle Stored procedure aggiunte o rigenerate, pertanto è indispensabile per noi dare loro questi permessi nel modo più semplice ed indolore possibile.
Questa Stored procedure, Made in Luca Del Mestre (esimio collega e DBA oracle) piuttosto grezza ma certamente pronta ad essere resa più sofisticata quando necessario, da il permesso di EXECUTE su tutte le SP utente di un Database e il permesso di SELECT su tutte le User Defined Functions.
USE [MyDb]
GO
/****** Object: StoredProcedure [dbo].[SpGrantExecSpAndFn] Script Date: 06/19/2008 11:53:58 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Sabrina C.
-- Create date: 18/06/2008
-- Description: Stored procedure per assegnare diritti di esecuzione SP + FN ad un utente o Ruolo
-- =============================================
ALTER PROCEDURE [dbo].[SpGrantExecSpAndFn]
@Utente nvarchar(128)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE @Comando nvarchar(1000);
DECLARE Rs cursor FOR
select 'GRANT EXECUTE ON ' + name + ' TO ' + @Utente +';'
Comando from sys.objects where [type] IN ( 'P', 'FN' )
union
select 'GRANT SELECT ON ' + name + ' TO ' + @Utente +';'
Comando from sys.objects where [type] IN ( 'U', 'IF', 'TF', 'V' )
OPEN Rs
FETCH NEXT FROM Rs INTO @Comando
WHILE @@FETCH_STATUS = 0 BEGIN
PRINT @Comando
Exec(@Comando)
FETCH NEXT FROM Rs INTO @Comando
END
CLOSE Rs
DEALLOCATE Rs
END
Il Parametro @Utente può essere il nome di un utente del DB oppure il nome di un Ruolo Utente che può essere generato all'interno del DB e mappato su tutti gli utenti che necessitano di connettersi al nostro Database ed utilizzano Stored Procedure e User Defined Functions.
Tags: SQL, Query, StoredProcedure, SqlServer
Usualmente è consigliabile evitare di usare il metodo Copia - Incolla per installare un Database SQL da un cliente. Però è comodo :).
Questa comodità, se non si presta un po' di attenzione copiando e svuotando il DB di prova su cui abbiamo fatto tutti i test e i pastrocchi in fase di sviluppo, può portare ad alcuni fastidiosi problemi. Ne affronto uno molto comune.
Nel Server SQL di sviluppo ho mappato degli utenti di test, alcuni sono Utenti Windows, altri sono Utenti SQL con diversi tipi di permessi per fare i test di accesso ai vari oggetti del DB.
Ovviamente, sul server in Produzione presso il cliente i miei utenti di dominio certamente non esistono come pure non esistono gli utenti SQL che ho creato appositamente.
Copiando ed incollando il DB di Sviluppo sul Server di Produzione, ottengo che il Database ha una serie di Utenti mappati che non hanno un login corrispondente sul Server.
Se provo a cancellarli, usualmente posso farlo e quindi il problema si risolve da Sql management studio oppure con una DROP USER, se però come ho fatto io, l'utente DB per qualche strano esperimento è proprietario di uno SCHEMA sul database, ancorché detto SCHEMA non contenga oggetti. La cancellazione darà errore. Ecco alcuni utili comandi TSQL Per risolvere i problemi di questo tipo:
-- Per Assegnare un Login valido ad un Utente il cui Login
-- non esiste sul nuovo server
USE [MyDb]
GO
ALTER USER MyUser WITH LOGIN = SqlLogin;
GO
-- Il Login nuovo può essere un login SQL oppure Windows
-- (Assumo che tutti sappiate la differenza fra Login di
-- SQL Server e User del Database).
-- Per spostare la proprietà di uno schema da un utente ad
-- un altro (io nell'esempio ho dato l'ownership al DBO ma
-- potrebbe anche essere un utente diverso del DB.
USE [MyDb]
GO
ALTER AUTHORIZATION ON SCHEMA::MySchema TO dbo;
GO
-- Per eliminare l'utente dopo averlo dotato di un login
-- ed avergli tolto la proprietà di qualsivoglia schema
USE [MyDb]
GO
DROP USER MyUser
GO
ALTER USER possiede altre opzioni piuttosto utili per modificare le caratteristiche dell'utente del DB, perciò fate un giro sui Books Online di SQL Server.
Tags: SQL, Database, Utenti, SqlServer
Ieri l'altro, in ufficio mi si è presentato il problema in oggetto, avevo infatti bisogno di modificare il campo principale per il legame fra i dati delle tabelle di un database.
Questo codice, un Intero, doveva essere modificato passando da 1 a 2, ovviamente essendo un vincolo di Foreign key su tutte le tabelle era un problema complesso da risolvere e il solo modo per farlo era quello di copiare tutti i dati tabella per tabella per poi rimuovere i dati errati.
Peccato però che essendovi alcune tabelle con relazione Padre Figlio sulla propria PK sarebbe stato un disastro trovare modo di fare la copia.
Togliere tutti i vincoli del DB a manina sarebbe stato piuttosto difficile, perché poi avrei anche dovuto rimetterli tutti perciò ho dovuto fare dei salti mortali tripli con carpiato quadruplo da un trampolino da 15 metri ...
Poi, su suggerimento del mio collega Luca, che mi ha fatto notare come in Oracle esista un comando per disabilitare i vincoli in modo temporaneo per risolvere queste problematiche, mi sono detta, ma SQL Server non può avere qualcosa di meno di Oracle sennò come fa a fargli concorrenza seriamente :D:D:D. Sono andata a cercare in giro ed ho trovato questo codice che pubblico per non dimenticarmene sperando di dare una mano a chi dovesse trovarsi nella stessa necessità.
Ovviamente ricordo a tutti coloro che volessero usare questo codice, che prima di farlo devono essere perfettamente certi di cosa stanno facendo, in quanto se non gestiscono correttamente le modifiche che fanno potrebbero creare inconsistenze nel database pertanto, prima di usare questo codice si deve fare un Backup Full, Meglio due, inoltre suggerisco, se dovesse succedere di farlo su un DB di produzione, di fare prima un test su una macchina di prova per poi eseguire lo script sulla macchina vera. A BUON INTENDITOR POCHE PAROLE!
--Disattivazione di tutti i vincoli
exec sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
--Disattivazione di tutti i trigger
exec sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'
-- Inserire qui il codice da eseguire per modificare il DB
-- Es.
-- Update TBData Set IDCommon = 2 WHERE IDCommon = 1
--Riattivazione di tutti i vincoli
exec sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
--Riattivazione di tutti i trigger
exec sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL'
Tags: SQL, Database, Vincoli, SqlServer
In seguito ad una richiesta fattami, ho cercato un vecchio esempio di codice per disegnare un rettangolo con gli angoli smussati su un qualsiasi contesto grafico.
Ho predisposto un esempio in area risorse, lo scaricate accedendo a questa pagina ovviamente dovete essere utenti registrati :).
L'esempio in VB e C# disegna il rettangolo su una Picture Box, ovviamente potete disegnarlo su qualsiasi oggetto di tipo Graphics.
Tags: grafica classi
sabato 12 aprile 2008
#
Faccio un Post per interposta persona, infatti questa soluzione arriva dal mio nuovo collega, Luca Del Mestre che spero di convincere presto a scrivere personalmente su DotNetWork.
Scenario:
Spesso ci troviamo ad avere la necessità di leggere da Database un singolo record di una tabella, a volte contiene dei parametri da usare nell'applicazione, a volte contiene il dettaglio di un particolare dato, è comunque comune alle applicazioni che lavorano con i DB.
Se la query che ci aspettiamo ritornare una riga non torna nulla, spesso, il codice necessario a gestire questo tipo di accadimento è lungo e decisamente poco utile, farebbe invece comodo e ci permetterebbe di risparmiare parecchio codice e parecchi grattacapi se la query ritornasse una riga in ogni caso, solamente facendo in modo che una riga non valida sia vuota.
Luca, che è un DBA Oracle, mi ha mostrato come in Oracle vi sia una sintassi nel dialetto SQL che permette di ottenere questo tipo di funzionalità, SQL Server ne ha una corrispondente però, in SQL 2005 applicandola si riceve un messaggio di errore che la indica come parte di SQL 2000 da non usarsi se non modificando il livello di compatibilità del db alla versione 2000. Ma ovviamente c'è sempre un metodo alternativo per risolvere il problema.
L'esempio che vi posto è creato su una ipotetica tabella Users che mappa gli utenti Windows che hanno accesso ad una applicazione su database.
La tabella:
USE [paperinik]
GO
CREATE TABLE [dbo].[TbUsers](
[IDUser] [uniqueidentifier] NOT NULL,
[WinUser] [nvarchar](128) NOT NULL,
[WinDomain] [nvarchar](128) NOT NULL,
[UserName] [nvarchar](255) NOT NULL,
[DataLogin] [datetime] NULL,
[IDAzienda] [nchar](3) NULL,
[InternalMail] [nvarchar](128) NULL,
[Obsoleto] [bit] NULL,
[ObsoletoDal] [datetime] NULL,
[IsAdmin] [bit] NULL,
[IsSupervisor] [bit] NULL,
[DirectConnection] [bit] NULL,
[LastUpdated] [timestamp] NULL,
CONSTRAINT [PK_TbMcpUsers] PRIMARY KEY NONCLUSTERED
(
[IDMcpUser] ASC
) ON [PRIMARY]
) ON [PRIMARY]
La classe di test:
public static class User
{
private const string SQL_CnString = "integrated security = SSPI; data source = .; initial catalog = paperinik;";
public const string FLD_IDUser = "IDUser";
public const string FLD_WinUser = "WinUser";
public const string FLD_WinDomain = "WinDomain";
public const string FLD_UserName = "UserName";
public const string FLD_IsAdmin = "IsAdmin";
private const string SQL_ValidateUser = @"
SELECT [IDUser]
,[WinUser]
,[WinDomain]
,[UserName]
,[DataLogin]
,[IDAzienda]
,[InternalMail]
,[Obsoleto]
,[ObsoletoDal]
,[IsAdmin]
,[IsSupervisor]
,[DirectConnection]
,[LastUpdated]
FROM TbUsers usr
RIGHT OUTER JOIN (SELECT @WinUser ut, @WinDomain do) fake
ON (usr.WinUser = fake.ut AND usr.WinDomain = fake.do)
";
private const string TXT_UtenteNonValido = "Utente non valido";
private static readonly string mClassName = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
private static DataRow mUserRow;
public static Guid IDUser
{
get
{
if (mUserRow[FLD_IDUser] == System.DBNull.Value)
{
return Guid.Empty;
}
else
{
return (Guid)mUserRow[FLD_IDUser];
}
}
}
public static string WinUser
{
get
{
if (mUserRow[FLD_WinUser] == System.DBNull.Value)
{
return null;
}
else
{
return (string)mUserRow[FLD_WinUser];
}
}
}
public static string WinDomain
{
get
{
if (mUserRow[FLD_WinDomain] == System.DBNull.Value)
{
return null;
}
else
{
return (string)mUserRow[FLD_WinDomain];
}
}
}
public static string UserName
{
get
{
if (mUserRow[FLD_UserName] == System.DBNull.Value)
{
return TXT_UtenteNonValido;
}
else
{
return (string)mUserRow[FLD_UserName];
}
}
}
public static bool IsAdmin
{
get
{
if (mUserRow[FLD_IsAdmin] == System.DBNull.Value)
{
return false;
}
else
{
return (bool)mUserRow[FLD_IsAdmin];
}
}
}
public static bool IsValid()
{
return( UserName != null && UserName != TXT_UtenteNonValido);
}
public static void ValidateUser(string pWinUser, string pWinDomain)
{
using (SqlConnection cn = new SqlConnection())
{
SqlParameter[] sqlParam = new SqlParameter[] {
new SqlParameter( "@WinUser", pWinUser ),
new SqlParameter( "@WinDomain", pWinDomain )
};
cn.ConnectionString = SQL_CnString;
cn.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = cn;
cmd.CommandText = SQL_ValidateUser;
cmd.Parameters.AddRange(sqlParam);
SqlDataReader dr = cmd.ExecuteReader();
if (dr.HasRows)
{
DataTable dt = new DataTable();
dt.Load(dr);
mUserRow = dt.Rows[0];
}
cn.Close();
}
}
}
Che cosa otteniamo provando ad usare la classe in una applicazione Windows dove io ho inserito un record per il signor M.Rossi e invece non ho inserito il signor G.Rossi?:
Il signor G.Rossi non è un utente mappato.
Il signor M.Rossi invece è mappato sulla tabella.
Dov'è il trucco per far si che la query che potete notare all'inizio della classe di test ritorni sempre un record:
SELECT [IDUser]
,[WinUser]
,[WinDomain]
,[UserName]
,[DataLogin]
,[IDAzienda]
,[InternalMail]
,[Obsoleto]
,[ObsoletoDal]
,[IsAdmin]
,[IsSupervisor]
,[DirectConnection]
,[LastUpdated]
FROM TbUsers usr
RIGHT OUTER JOIN (SELECT @WinUser ut, @WinDomain do) fake
ON (usr.WinUser = fake.ut AND usr.WinDomain = fake.do)
Il segreto è la RIGHT OUTER JOIN utilizzata per costruire un filtro furbo, ove invece di fare una semplice
WHERE
WinUser = @WinUser AND WinDomain = @WinDomain
Usiamo i 2 parametri del filtro per costruire una tabella al volo che chiamiamo fake (falsa)
(SELECT @WinUser ut, @WinDomain do) fake
E facciamo una RIGHT JOIN fra questa tabella e la tabella utenti utilizzando la clausola OUTER per cui per ogni riga della tabella posta a destra della JOIN viene creata una riga nella tabella risultante, che, quando non vi fossero valori nella tabella LEFT (la nostra USER) riempie i campi con NULL.
In questo modo, otteniamo un record valido per M.Rossi e un record vuoto per G.Rossi. Come possiamo notare, la nostra classe di test mappa direttamente i campi della DataRow sulle property con la sola gestione del DBNull, senza questa soluzione, per ottenere una classe funzionante, avremmo dovuto mappare i campi su variabili a livello di classe e fare una funzione di Clear che le svuota quando non c'è una riga che possa aggiornare i dati. Avremmo inoltre dovuto aggiornare le variabili a livello di classe dalla DataRow aggiungendo codice ulteriore e OverHead al nostro sistema.
Tags: SQL, Query, CheckIdent,SqlServer
venerdì 28 marzo 2008
#
Un post polemico, ma senza cattiveria:
Per quanto Windows Vista mi piaccia per molte cose, in esso sono state inserite tutta una serie di funzionalità e automatismi che aiutano quello che dalle nostre parti bonariamente definiamo "Utente Medio Mona" che invece quando l'utente è un esperto che usa il computer per lavorare non fanno altro che Provocare frustrazione e un numero di improperi indirizzati agli sviluppatori di Vista che faranno si che tutti noi quando passeremo a miglior vita finiamo nel più profondo degli inferi.
Ne prendo un paio fra tante, per cui stamattina Rudy, che non è usualmente uno che impreca, ha imprecato per un'ora intera. Che cos'è questa cosa così orrenda:
Molto semplicemente, Vista decide la modalità di visualizzazione del contenuto di una cartella in base al contenuto stesso e non in base al settaggio che noi diamo e questo anche se da buoni programmatori, tiriamo fuori da dove le hanno nascoste le opzioni di cartella sul file manager e settiamo la cartella come ci pare, cliccando il bottone "Applica a tutte le cartelle".
A quanto pare, Vista non ci sente bene quando gli si da degli ordini e quindi di sua volontà comunque cambia la modalità di visualizzazione come gli pare se ritiene che il contenuto non vada visto come noi gli abbiamo detto.
Non solo, se attiviamo l'opzione "Memorizza le impostazioni assegnate per ogni cartella" per default, il numero massimo di cartelle per cui memorizzare le impostazioni è 400 che, come sempre, per un Utente Normale sono tante, ma per un utente esperto che riclassifica in modo coerente i suoi archivi non sono poi tante. Soprattutto se comunque sul nuovo PC trasferisce sempre i 10/12 anni di lavoro che si porta dietro, per quanto abbia archiviato le cose più obsolete ed ormai inutili su DVD.
Fortunatamente Il Registry editor ancora funziona, ecco come modificare le chiavi per disattivare queste due feature:
; Reset and delete all saved folder customizations and settings.
[-HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU]
[-HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags]
; Turn off Vista auto folder type template discovery.
[HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell]
"FolderType"="NotSpecified"
; Modify the storage space to 10000 for saving of up to 10000 folder settings.
[HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell]
"BagMRU Size"=dword:00002710
Mentre stavo scrivendo, Rudy ne ha trovata un'altra, ovvero l'opzione per disattivare la Disk Virtualization di Vista. Mi piacerebbe che chi l'ha inventata me ne descrivesse l'utilità, questa funzione, che rallenta notevolmente la velocità di esecuzione di Vista e tutte le attività sul disco, fa in modo che le cartelle sul disco si chiamino Program Files ma io "Utente medio mona italiano" veda scritto "Programmi", oppure la cartella sul disco si chiami "Images" ed io veda "Immagini".
Ovviamente, all' "Utente Medio Mona" certamente questa funzionalità renderà la vita più semplice perché essendo appunto "Medio Mona" non sa una parola di inglese e quindi si troverebbe molto male se le cartelle avessero nomi diversi da quelli a cui è abituato.
Ma al programmatore o comunque utente esperto, che si vede sul file manager un nome di file, poi va sulla shell e si rende conto che il nome vero è diverso, gli vengono i fumi.
Grazie a questa funzionalità e ad un programmatore non troppo preciso, io mi sono ritrovata sul mio PC due diverse cartelle che sul file manager si chiamavano Programmi, perché il software creato da questo programmatore mi aveva generato la cartella Programmi, Il Sistema operativo Rinominava Program Files Programmi quindi io avevo due cartelle virtualmente uguali. Non solo, non era neppure possibile rimuovere la cartella sbagliata perché ovviamente Programmi è una cartella di sistema e su Vista anche l'amministratore non può fare quello che gli pare sul disco.
Soluzione a priori, dopo formattazione, che abbiamo adottato: Sistema operativo in Inglese, Sistema di sviluppo in Inglese. Ma comunque, questa funzionalità che in Inglese dovrebbe essere superflua, è attivata di default.
A proposito, se per caso vi serve si disattiva così:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System]
"EnableVirtualization"=dword:00000000
La domanda che mi viene sempre posta come sviluppatore quando propongo una funzionalità è la seguente:
A COSA SERVE?
Seguita da una seconda domanda difficilissima:
MA IL CLIENTE LO PAGA?
Ovvero, tradotto in gergo tecnico, qual'è il valore aggiunto per l'utente/cliente in questa funzionalità?
Se c'è chi è in grado di farmi vedere l'utilità di queste due Feature, lo ringrazierò, però mi piacerebbe anche che tutte queste funzionalità avessero una bella lista di checkbox sul control panel per poter essere attivate o disattivate in base alle preferenze di chi lavora, ciò renderebbe gli utenti esperti in grado di lavorare in modo rilassato, ringraziando chi gli ha costruito un sistema che possono gestire come gli pare, probabilmente darebbe modo ai sistemisti di tarare le configurazioni delle macchine sul tipo di lavoro degli utenti e soprattutto, ci risparmierebbe il quinto girone dell'inferno.
Attendo Fulmini, saette e Forconi tridenti :)
Tags: Vista
lunedì 24 marzo 2008
#
I Filegroup di SQL Server sono una funzione ottima che permette di suddividere in modo "Verticale" un database molto complesso e con molte tabelle dando modo al DBA di raggruppare le tabelle in modo funzionale per poterle suddividere in spazi diversi (Files su disco/dischi) oppure per organizzare in modo coerente i Backup (Tabelle anagrafiche non modificate spesso con Backup più semplici, tabelle ad alto tasso di aggiornamento con backup articolati e ad intervalli di tempo brevi.
Ma cosa succede se per caso dopo aver creato un filegroup ci si rende conto che una tabella va spostata? Ovviamente tutto è previsto, ecco il codice per poter spostare una tabella da un Filegroup ipotetico diverso dallo standard (Primary) appunto al Filegorup standard.
Per prima cosa una query per sapere quali tabelle sono in un FileGroup:
SELECT
'table_name'=object_name(idx.id)
,idx.indid
,'index_name'=idx.name
,idx.groupid
,'filegroup'=fgr.name
,'file_name'=dbf.physical_name
,'dataspace'=spc.name
FROM sys.sysindexes idx
,sys.filegroups fgr
,sys.database_files dbf
,sys.data_spaces spc
WHERE objectproperty(idx.id,'IsUserTable') = 1
AND fgr.data_space_id = idx.groupid
AND fgr.data_space_id = dbf.data_space_id
AND fgr.data_space_id = spc.data_space_id
ORDER BY fgr.name, object_name(idx.id), groupid
GOE poi come si sposta una datatable da un filegroup a quello standard:
ALTER TABLE
[MyTable]
DROP CONSTRAINT [PK_MyTable]
WITH (MOVE TO [PRIMARY])
GO
ALTER TABLE
[MyTable]
ADD CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[IDMyTable] ASC
)
WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
Visto che sono stata accusata di non aver mai trasferito lo storico del mio Blog dal vecchio sito al nuovo, oggi dato che piove, fa freddo e mettere il naso fuori è da escludersi, uso la giornata di festa per inserire tutti i post storici riciclati dal vecchio Blog. Ho inserito la parola (Storico) fra parentesi, affinché non pensiate che mi è venuto un attacco di Frenesia dello Scrittore.
Inoltre, ho esagerato ed aggiungo un piccolo post derivato da un paio di query SQL che mi ero salvata.