giovedì 18 febbraio 2010

Filtrare i dati in SSAS con la funzione UserName()

 Mi è capitato di dover fare in modo che gli utenti che accedessero ad un Cubo di Analisys Services 2005 vedessero solo i dati di loro competenza.

In pratica nel mio caso avevo una dimensione di tipo gerarchico che mi definiva la gerarchia aziendale del personale (tipica tabella degli utenti con l'id del superiore) e il requisito era quello che ogni utente avrebbe dovuto vedere  solo i dati che si riferivano a lui stesso o ai suoi sottoposti.

Imbattendomi nella configurazione dei permessi di SSAS ho trovato la seguente soluzione che utilizza la funzione UserName() che restituisce l'utente corrente:

- Innanzitutto è necessario creare un Ruolo ad hoc all'interno del cubo a cui assegnare tutti gli utenti che devono essere soggetti a tale comportamento.

- Se si vuole fare in modo che l'utente veda esclusivamente solo la gerarchia che lo riguarda (gli altri utenti non saranno nemmeno visibili), è necessario impostare nella configurazione del ruolo all'interno del tab Dimension Data nella sezione Allowed member set la seguente espressione MDX :
      
             DESCENDANTS( [UtentiDIM].[Gerarchia].members("[UtentiDIM].[Gerarchia].["+UserName()+"]") )

- Se si vuole invece limitare esclusivamente la visualizzazione delle misure per la gerarchia che lo riguarda senza limitare la visualizzazione dell'intera gerarchia, è necessario impostare nella configurazione del ruolo all'interno del tab CellData nella sezione Allow reading of cube content la seguente espressione MDX:

             ISANCESTOR( [UtentiDIM].[Gerarchia].members("[UtentiDIM].[Gerarchia].["+UserName()+"]"),[UtentiDIM].[Gerarchia].CURRENTMEMBER )
             OR  [UtentiDIM].[Gerarchia].members("[UtentiDIM].[Gerarchia].["+UserName()+"]") IS [UtentiDIM].[Gerarchia].CURRENTMEMBER

Ovviamente tali espressioni valgono nel caso più semplice che il nome del membro corrisponda esattamente agli username degli utenti. In caso contrario è necessario modificare leggermente l'espressione MDX.

martedì 16 febbraio 2010

Unit test del Data Layer con TransactionScope

Nel momento in cui si deve eseguire uno Unit Test per testare il funzionamento delle classi per l'accesso ai dati, si possono percorrere due strade:

- la prima è quella di utilizzare un database di test che risiede completamente in memoria, come ad esempio SqlLite, un ottimo motore SQL che possiede appunto la caratteristica di poter girare senza installazioni e senza file fisici. Ovviamente il vantaggio di questa soluzione è che ad ogni avvio dei nostri test, abbiamo sempre a disposizione un database pulito. Per inciso, ricordo che Nhibernate possiede una comoda funzionalità di generazione degli script per la creazione dello schema del database a partire dal file di mapping.

- la seconda è quella invece di utilizzare un vero e proprio database di test.

Nonostante sia preferibile la prima soluzione, in alcuni casi questa è una strada difficilmente percorribile, ad esempio quando si utlizzano tipi di dati che non sono supportati in SqlLite.

Utilizzando un database di test, è necessario però garantire che tutte le operazioni che vengono eseguite sul database durante i test, debbano essere ripristinate al loro termine.
A tal fine torna molto utile l'utilizzo della classe TransactionScope (assembly System.Transaction), che ci garantisce che tutti comandi lanciati sul database siano contenuti all'interno di una transazione del quale verrà invocato il RollBack al termine dei test.

Nell'esempio utilizzo NUnit come framework per lo unit test, ma il concetto è valido ovviamente per tutti i framework.

        protected TransactionScope _transactionScope;

        [SetUp]
        public void Setup()
        {
            _transactionScope = new TransactionScope();
        }

        [TearDown]
        public void TearDown()
        {
            _transactionScope.Dispose();
        }

Se questo codice lo inseriamo in una classe base che tutte le classi di test devono estendere, ecco che abbiamo un sistema sicuro per eseguire i nostri test delle classi del Data Layer

giovedì 11 febbraio 2010

Nhibernate custom type mapping e dati geospaziali

Ormai è da un pò di tempo che utilizzo Nhibernate nei progetti che seguo e ogni tanto capita di dover affrontare nuove esigenze.
Riporto qui il caso in cui si debba avere a che fare con il mapping di un classe che possiede proprietà di un tipo che non rientra tra quelli gestiti direttamente da Nhibernate.

A titolo di esempio riporto il caso in cui una classe abbia una proprietà di tipo SqlGeography. La classe SqlGeography è contenuta nell'assembly Microsoft.SqlServer.Types ed è la classe che rappresenta il data type Geography di SQL Server 2008 non direttamente supportato da NHibernate.
Il data type Geography è la rappresentazione binaria delle coordinate geografiche di un punto sulla terra (coppia latitudine longitudine).
Quello che vogliamo ottenere è il mapping della proprietà di tipo SqlGeography e il campo sul database di tipo Geography (rappresentazione binaria del testo "POINT (latitudine longitudine)")

Per ottenere ciò è necessario innanzitutto creare una nuova classe che abbia lo scopo di definire il modo in cui NH debba eseguire il mapping sul database. Tale classe deve ereditare dall'interfaccia IUserType (namespace NHibernate.UserTypes) e implementare i seguenti metodi:

public class SqlGeographyUserType : IUserType
    {
        public object DeepCopy(object value)
        {
            if (value == null)
                return null;
            var sourceTarget = (SqlGeography)value;
            SqlGeography targetGeography = SqlGeography.Point(sourceTarget.Lat.Value, sourceTarget.Long.Value,
                                                              sourceTarget.STSrid.Value);
            return targetGeography;
        }
       
        public object Assemble(object cached, object owner)
        {
            return DeepCopy(cached);
        }
       public object Disassemble(object value)
        {
            return DeepCopy(value);
        }

Assemble e Disassemble vengono chiamati da NH rispettivamente durante la lettura e la scrittura nella cache di secondo livello, dunque si devono preoccupare di restituire o creare l'oggetto nella sua versione in cache. In questo caso l'oggetto in cache sarà identico a quello restituito utilizzando il metodo DeepCopy.

        public bool Equals(object x, object y)
        {
            if (ReferenceEquals(x, y))
                return true;
            if (x == null || y == null)
                return false;
            return x.Equals(y);
        }

Il metodo Equals è utilizzato da NH per verificare se la proprietà è diversa da quella memorizzata nella snapshot salvata in memoria, così da provvedere al salvataggio su database.

        public int GetHashCode(object x)
        {
            return x.GetHashCode();
        }

        public bool IsMutable
        {
            get { return true; }
        }

IsMutable deve restituire true se la classe è Mutable. Per le classi non Mutable infatti (cioè quelle per cui NH non debba fare l'insert e l'update) NH ha alcune ottimizzazioni delle performance.

        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            object prop1 = NHibernateUtil.String.NullSafeGet(rs, names[0]);
            if (prop1 == null)
                return null;
            SqlGeography geo = SqlGeography.Parse(new SqlString(prop1.ToString()));

            return geo;
        }

NullSafeGet viene chiamato da NH per ottenere l'istanza della proprietà mappata (nel nostro caso l'oggetto di tipo SqlGeograhy) a partire dal datareader utilizzato per leggere dal database. Dalla lettura della stringa del tipo "POINT (lat lon)" otteniamo un istanza del tipo SqlGeography.

        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            if (value == null)
                ((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
            else
                ((IDataParameter) cmd.Parameters[index]).Value = ((SqlGeography) value).STAsText().Value;
            ;
        }

NullSafeSet
viene utilizzato invece per impostare i parametri da passare al Command quando NH esegue un'insert o un'update della proprietà sul database.
In questo caso passiamo a SQL la rappresentazione in stringa dell'oggetto di tipo SqlGeography ("POINT (lat lon)"). NH provvederà dunque ad eseguire una insert nel formato: INSERT INTO Table1 (id,Localization) VALUES (2,'POINT(lat lon)')

        public object Replace(object original, object target, object owner)
        {
            return DeepCopy(original);
        }

        public Type ReturnedType
        {
            get { return typeof (SqlGeography); }
        }

        public SqlType[] SqlTypes
        {
            get { return new[] {NHibernateUtil.String.SqlType}; }
        }
}

La proprietà SqlTypes è utile per indicare a NH quale tipo deve utilizzare nella creazione delle istruzioni DDL per la generazione dello schema.

Ora che la classe è pronta possiamo definirla nel file di mapping:

<property name="location" column="GeoLocation" type="MyNamespace.SqlGeographyUserType, MyNamespace.NhibernateMappings" />

Attenzione, nel caso vogliate che il vostro IUserType debba mappare più di una proprietà, e vogliate utilizzare quest'ultime nelle query HQL (ad esempio nel caso volessimo mappare singolarmente lat e lon e utilizzare nelle query HQL una sintassi del tipo localization.lat=...) allora è necessario che il vostro SqlGeographyUserType non erediti da IUserType ma da ICompositeUserType che aggiunge qualche metodo in più all'interfaccia IUserType.

venerdì 5 febbraio 2010

Eccomi qua!

Ciao a tutti,
questo è il primo post quindi è doverosa una presentazione.

Mi chiamo Daniele Natali, sono un consulente informatico e attualmente mi occupo della gestione di alcuni progetti presso il cliente per cui lavoro.
Ma oltre ad essere la mia fonte di guadagno, l'informatica è anche la mia passione. Perciò in questo blog troverete articoli, notizie legate al mondo informatico ed in genere a quello tecnologico :)

A tutti quelli a cui, come me, piacciono queste cose...a presto!!