Analysis Services Report Pack

Publié par Fabrice Michellonet sous le(s) label(s) , le 20 janvier 2011

Avant-hier, mon client m'a demandé s'il était possible d'avoir une "cartographie" de sa base OLAP que nous sommes en train de construire.

Par cartographie, il entendait a minima avoir un listing de toutes les mesures, dimensions et niveaux existants. Ma première réaction fut de vérifier si BIDS Helper n'avait pas ce genre de feature cachée dans un coin. Circulez rien à voir de très intéressant de ce côté-là!

Je me suis ensuite rué sur Google pour voir s'il n'y avait pas un SQL Server Report Pack comme celui qui était apparu pour SQL Server 2005, mais axé Analysis Services. Rien non plus en direct de chez Redmond.
Par contre en creusant un peu je suis tombé sur l'excellent blog de la société Capstone et leur superbe Analysis Services 2008 Metadata Report Pack.

Voici un aperçu du rendu des rapports Reporting Services mis à disposition :

Database Cube Dimension

Et pour faire plaisir à mon ami François, il y a même des infos sur le Data Mining :

Dimension

Pour installer le pack il vous faudra créer un serveur lié dans SQL Server; Voici le script SQL a mettre en adéquation avec la configuration de votre machine :

EXEC master.dbo.sp_addlinkedserver @server = N'SSAS_METADATA', @srvproduct=N'SSAS', @provider=N'MSOLAP', @datasrc=N'localhost', @provstr=N'Provider=MSOLAP.4', @catalog=N'Adventure Works DW 2008R2'
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'SSAS_METADATA',@useself=N'False',@locallogin=NULL,@rmtuser=NULL,@rmtpassword=NULL

Puis déployer les rapports sur Reporting Services.

Je me permets de reposter l'ensemble de la solution de Dan Meyers ici; version dans laquelle j'ai corrigé les connexions en dur par une connexion vers le serveur lié.

En dehors de répondre au besoin ponctuel de mon client, je me dis que ces rapports pourraient très bien trouver leur place dans une documentation technique à réaliser en fin de projet.

Injection HTML à base d'HTTPModule

Publié par Fabrice Michellonet sous le(s) label(s) , le 18 janvier 2011

Dernièrement j'ai échangé avec mon Boss, Jérôme, sur les possibilités d'enrichir un site web non dévellopé en .NET mais qui serait hosté dans IIS.
On pourrait se mettre à apprendre le Python... mais voila on est un peu fainéant :)

Une des solutions que je lui ai proposé étais de tirer parti du mécanisme d'extensibilité du pipeline de IIS7 afin d'intervenir directement sur le code HTML renvoyé au navigateur.

Green Injection

Je me propose donc de présenter dans les grandes lignes comment mettre en place ce genre de solution.
Pour illustrer mes propos simplement nous allons nous rajouter un div en fin de page, le div contenant l'heure du système.

On commence par créer notre HTTPModule et on répond à l'évenement BeginRequest :

using System;
using System.IO;
using System.Web;

namespace InjectorModule
{
    public class Injector : IHttpModule
    {
        private HttpApplication _application;

        public void Init(HttpApplication context)
        {
            context.BeginRequest += OnBeginRequest;
            _application = context;
        }

        private void OnBeginRequest(object sender, EventArgs e)
        {
            Stream filter = FilterFactory.GetFilter(_application);
            if (filter == null)
                return;

            _application.Response.Filter = filter;
        }

        public void Dispose() {}
    }
}

On délègue à une factory le role de créer ou non un filter en fonction de règles qui ne sont pas connues par le Module.
Le module reste ainsi une brique uniquement technique technique.

Passons à la factory

using System.IO;
using System.Web;

namespace InjectorModule
{
    internal static class FilterFactory
    {
        public static Stream GetFilter(HttpApplication application)
        {
            if (application == null || application.Response.ContentType == null)
                return null;

            if (application.Response.ContentType.ToUpperInvariant().Contains("HTML"))
                return new AppendDateTimeFilter(application.Response.Filter, application.Request.ContentEncoding);

            return null;
        }
    }
}

Ici pas de règles métiers compliquées, on ajoute un Filter si le content type est de type HTML.

Et finalement voici l'implémentation du Filter qui tranforme la réponse envoyée aux navigateurs :

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace InjectorModule
{
    internal class AppendDateTimeFilter : Stream
    {

        private readonly Stream _inputStream;
        private readonly Encoding _encoding;
        private readonly StringBuilder _responseHtml;

        public AppendDateTimeFilter(Stream input, Encoding contentEncoding)
        {
            _inputStream = input;
            _encoding = contentEncoding;
            _responseHtml = new StringBuilder();
        }

        #region Filter overrides

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return true; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override void Close()
        {
            _inputStream.Close();
        }

        public override void Flush()
        {
            _inputStream.Flush();
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position { get; set; }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _inputStream.Seek(offset, origin);
        }

        public override void SetLength(long length)
        {
            _inputStream.SetLength(length);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _inputStream.Read(buffer, offset, count);
        }
        #endregion

        public override void Write(byte[] buffer, int offset, int count)
        {
            try
            {
                string bufferContent = _encoding.GetString(buffer);

                // Wait for the closing tag
                Regex eof = new Regex("", RegexOptions.IgnoreCase);

                _responseHtml.Append(bufferContent);
                if (!eof.IsMatch(bufferContent))
                    return;

                // Transform the response and write it back out
                string finalHtml = _responseHtml
                                        .Replace("", string.Format(@"
{0}
", DateTime.Now)) .ToString(); // Send. byte[] data = _encoding.GetBytes(finalHtml); _inputStream.Write(data, 0, data.Length); } catch (Exception) { } } } }

Pour tester rien de plus simple, on compile l'assembly, on la place dans le dossier bin du site web que l'on veux trafiquer.
Dans notre cas, nous avons aussi du créer le dossier bin car non existant dans l'appli Web Python.
Le fait de placer l'assembly dans un dossier bin, est une contrainte technique imposée par IIS; je n'ai pas trouvé d'alternatives et a priorit il n'y a pas de configuration qui pourrait influer sur cette contrainte.
Finalement dans le gestionnaire IIS, rendez vous dans la section Modules de votre application Web.
En ouvrant la fenetre d'ajout de modules, votre assembly sera désormais présente; Sélectionnez la, et pointez votre navigateur sur ce dernier.

Tadam!!! l'heure s'affiche tout en bas :)

J'esperes que cette solutions vous ouvre de nouvelles possibilités.

Un dernier mot pour vous conseiller l'excellente librairie htmlagilitypack qui vous donnera les moyens de parser facilement un document HTML, même malformé.

Identity ou UniqueIdentifier (Guid) ?

Publié par Fabrice Michellonet sous le(s) label(s) le 26 octobre 2010

Je dois être maudit ! Cela fait prés de 3 ans que sur tous les projets sur lesquels j’interviens, le sempiternel troll du Identity vs Guid revient me hanter.

Non, non, non… je ne suis pas un ayatollah de la base de donnée… je n’ai rien contre les GUID ! Mais pourtant j’ai quand même du mal à comprendre pourquoi on a tendance à en coller partout, même quand ca n’a aucun sens… et ca j’ai vraiment du mal…

Mettons notre casquette de dba (je sais… beurk !)

Le Guid c’est le mal (sauf pour la réplication ou l’on n’a pas le choix)… car :

  • en production, on a vu plus simple que d’écrire des jointures sur des choses aussi imbitables, et anti-mémorisable que B2F62BF0-5CE0-408c-B749-F37D76AF5629.

  • Les perfs sont juste pourries par rapports à celles que l’on obtiendrait avec un int. J’en entends déjà plus d’un ricaner et prêt à me soutenir le contraire….Bien ! Rendez-vous sur ce benchmark, et prenez même 10 minutes pour lancer le test chez vous… c’est flagrant !Ce qui est étonnant, c’est qu’un grand nombre de développeurs, y compris des gens très brillant, passent à côté de ce point… certain m’ont déjà soutenu le contraire… au secours !

Mettons notre casquette de développeur :

Le Guid c’est la meilleure invention depuis le garbage collector… ! Si si je fais mon gloubi-boulga et puis je referme le capot, ca marche à tout les coups… c’est génial le GUID.

Dans une architecture en couche, je peux générer des Id dans ma couche client, associer des entités entre elles… puis :

Si j’ai envie de les persister, pas de soucis et pas d’aller retour avec la le tiers de persistance (base de données), mes identifiants sont à coup sur uniques.

Si j’ai envie d’annuler tout ce que j’ai fais, bah je cache méthodiquement tout sous le tapis (lisez : je laisse travailler le garbage collector).

Tandis qu’un Int en tant qu’identifiant, qu’est ce que c’est embêtant… je crée une entité sur mon client mais tant que je n’ai pas fait d’aller retour avec ma base de donnée, je n’ai aucun moyen de savoir son identifiant…

Vous voulez associer un produit et sa catégorie de produit tout deux nouvellement crées ? Possible, mais on va devoir écrire un peu de code et faire des allers-retours avec la base.

Bref… vu d’en haut l’identity c’est chiant… on est d’accord.

PS : Pour ceux qui me diraient qu’on est pas obligé de faire des applications en couches séparées… bah…. ils ont raison, mais sur un projet un minimum important ca devient vite difficile à maintenir de tout coder dans la form (aussi bien web que win)… faut que vous jetiez un coup d’œil au TDD.

Voila, c’est la que commence à mon sens notre vrai travail. Il nous faut estimer les avantages et les inconvénients des deux approches, et faire un choix entre la performance pure de la base et grosso modo la vitesse de développement de l’application… en ces temps ou le mot d’agilité et dans la bouche de tous, le choix est vite fait… Promis m’sieur y’a quelques temps j’arrivais encore à faire le grand écart…

Bon tout ca c’est bien beau, mais alors quelqu’un pourrait m’expliquer pourquoi il m’arrive de tomber sur des bases qui sont truffées de GUID (seul type de donnée utilisé comme clé primaire des tables) lorsque celles-ci sont attaquées par une application en mode client-serveur ? Franchement, la, ca me dépasse !

Bon, aller pour ceux qui ont réussi à me lire jusqu’ici, voici quelques liens supplémentaires sur le sujet :

Les racines des problèmes que posent les GUID avec les index Clustered

la fonction SQL-SERVER NEWSEQUENTIALID() si on adore les GUID dans SQL et que l’on ne peut s’en passer.

Et le meilleur pour la fin, je vous laisse réfléchir sur la phrase suivante trouvée dans la MSDN :

Consider using the IDENTITY property when global uniqueness is not required, or when having a serially incrementing key is preferred.

Evidemment, si certain d’entre vous on envie de troller sur le sujet… les commentaires sont fait pour ca :)

JetBrains supporte l'Open Source

Publié par Fabrice Michellonet sous le(s) label(s) le 27 avril 2010

Comme vous le savez sûrement, la version 5 de Resharper est disponible depuis quelques temps, évidement pour supporter la version 2010 de Visual Studio; même si le produit est installable sur un Visual Studio 2008. Le but de ce post, n'est pas juste de vous annoncer la mise à dispo d'un soft et d'en faire la pub, c'est vraiment pas mon genre, et encore moins le but de ce blog.

Bon retour en arrière de quelques mois, je tombe (presque) par hasard sur le page de licensing de Resharper (à l'époque version 4.5) et je me disais que ca serait sûrement un bon investissement; Après tout, un code écrit plus vite, et avec moins de bugs (Modified Closure), c'est bon pour mes clients, ma boite et puis pour moi aussi au final. Bref, plusieurs type de licences sont listées sur le site, dont une Licence Open Source GRATUITE...

Ouarf ouarf ouaf, s'écrit mon côté pessimiste...

Ça c'est de la bonne pub gratuite.

J'approfondis un peu, il est dit qu'il faut présenter son projet Open Source, prouver sa participation dans le projet et une commission décidera si une licence gratuite sera attribuée etc...

Bon ok, en gros derrière dois y avoir une vieille boite mail que plus personne ne lit, et de toute façon ca m'étonnerait que les mecs aient le temps et l'envie de faire autre chose que coder pour leurs softs... alors de la à aller jeter un œil sur les projets Open Source soumis, puis faire une commission faudrait pas pousser quand même

Je vous l'accorde, ce jour la je devais être en mode Christophe... un peu beaucoup bougon! :)

Bon, pas très grave tout ca, car très peu de temps après je démarre une nouvelle mission (mon client actuel), et il se trouve que Resharper fait partie du toolkit de tout les développeurs... Ouf, j'vais pouvoir faire illusion, et faire croire que je sais un peu programmer.

Pas mal de temps passe, la question de l'acquisition de Resharper n'est plus trop d'actualité, jusqu'à ce que les betas de la version 5 ne commencent a être mise à dispo. Et puis lorsque la version finale sort, je ne peux m'empêcher de l'installer, pour 30 jours seulement en version demo... sniff.

Alors, il y a moins d'une semaine, je me lance, et soumet à la fameuse commission mon pet projet actuel (Claymore), sans trop de conviction. Et figurez vous qu'a peine 3 jours après (un dimanche exactement... bah oui les commissions de développeurs c'est en dehors des heures de travail) la bonne nouvelle tombe dans ma boite mail... j'ai le droit a une version Full de Resharper 5.0 pendant 1 an grâce à ce projet Open Source.

Donc déjà, je vous demande pardon mesdames et messieurs de chez JetBrains d'avoir douté de votre parole. Merci de cet effort que vous consentez pour améliorer la qualité du code Open Source. Et puis, j'suis sur que mes clients actuels et futures vous remercieront aussi... j'ferais moins de bugs grâce a vous.

Moralité, si vous avez un bout de code qui vaut le coup d'être mis à dispo des autres, et qui de toute façon ne vous rendra pas millionnaire, publiez-le, il vous permettra peut être d'avoir une licence gratuite de Resharper.

Update [28 Avril 2010] : Jérôme m'a fait remarquer que mon lien vers l'exemple de Modified Closure était erroné. C'est corrigé.

Overriding et Shadowing de méthodes en IL.

Publié par Fabrice Michellonet sous le(s) label(s) le 23 avril 2010

En ce moment je fais joujou avec les opcodes CIL qui se cachent sous notre bon vieux C#.

En voulant surcharger et cacher (shadow) successivement deux propriétés hérités, j'ai remarqué que le compilo générait une suite de metadata assez déroutante. Prenons l'exemple suivant :

public class BaseClass
    {
        public int Prop1 { get; set; }
        public virtual int Prop2 { get; set; }
        public virtual int Prop3 { get; set; }
    }

    public class DerivClass : BaseClass
    {
        public int Prop1 { get; set; }
        public new int Prop2 { get; set; }
        public new virtual int Prop3 { get; set; }
    }

Du coté de la classe de base on obtient les metadata suivantes sur le getter :

non virtual :

.method public hidebysig specialname instance int32 get_Prop1() cil managed
virtual :
.method public hidebysig specialname newslot virtual instance int32 get_Prop2() cil managed
remarquez l'utilisation conjointes des metadata virtual et newslot, je reviendrais dessus juste après.

Côté classe dérivée on obtient cela :

non virtual :

.method public hidebysig specialname instance int32 get_Prop1() cil managed
new :
.method public hidebysig specialname instance int32 get_Prop2() cil managed
virtual new :
.method public hidebysig specialname newslot virtual instance int32 get_Prop3() cil managed

Oulaaa... c'est la que ca se corse!! Dans la classe de base, comme je vous le faisait remarquer précédemment, le compilo utilise conjointement les metadata virtual et newslot. Logiquement, on aurait pu s'attendre a voir uniquement le virtual.

Dans la classe dérivée, le shadowing non virtuel

public new int Prop2 { get; set; }
produit des metadata token strictement identiques à une propriété normale; et le shadowing virtuel produit un IL identique à propriété virtuelle.

De ces constatation on peut en déduire que : Le token newslot, ne suffit pas à affirmer qu'il s'agit d'une shadowing methode (le getter non-virtuel de la classe de base et le getter new de la classe dérivée ayant les mêmes metadata).

Par contre, si une méthode contient le token virtual mais pas le newslot, alors c'est qu'il s'agit a coup sur d'un overriding et pas un shadowing.

Par déduction, on peut donc penser que si une méthode n'est pas un overriding (le token newslot est présent), alors il n'y a shadowing que si la signature [type de retour, nom et paramètres de la méthode] de la méthode existe également dans la classe de base.