PowerBI Report Server : Planifier le rafraîchissement des données via Script

L’API Rest de PowerBI Report Server

Suite au déploiement automatisé d’un rapport power BI vers le PowerBI Report Server (cf. PowerBI Report Server : Déployer et configurer un rapport via Script), une autre opération consiste à planifier le rafraîchissement du rapport.

A nouveau, nous pouvons faire appel à l’API Rest de PowerBI Report Server. Les planifications étant accessibles sur la route CacheRefreshPlans.

La planification

Pour créer une nouvelle planification, il s’agit d’utiliser sur cette route la méthode POST en lui fournissant un objet Json dont la structure est celle d’une planification.

Il faut y trouver :

  • Un propriétaire
  • Une description
  • L’élément à rafraîchir (représenté par son chemin virtuel)
  • Le type d’événement qui doit être DataModelRefresh
  • Une planification
  • Le cas échéants des valeurs pour les paramètres

Ce qui peut donner le script PowerShell suivant :

#Création de l'objet Json de planification
$payload = convertTo-Json (@{
"Owner" = "Domain\User";
"Description" = "Planification créée automatiquement"
"CatalogItemPath" = "/MonRapportPublie";
"EventType"="DataModelRefresh";
"Schedule" = @{"ScheduleID" = "";
"Definition" = @{
"StartDateTime" = "2018-11-14T16:00:00.00+02:00"
}
};
"ParameterValues" = @()
})


#Création de la planification sur le serveur
Invoke-WebRequest -Uri $powerBIReportsCacheRefreshPlansUri -Method POST -Body $payload -ContentType "application/json" -UseDefaultCredentials

Nous disposons désormais d’un rapport dont le rafraîchissement est planifié, la publication ayant été pilotée uniquement en PowerShell.

Publicités

PowerBI Report Server : Déployer et configurer un rapport via Script

L’API Rest de PowerBI Report Server

Dans le cadre d’un industrialisation du serveur de rapport, il est utile de pouvoir déployer, configurer et planifier le rafraîchissement de rapports PowerBI de manière automatisée.

PowerBI Report Server est une version enrichie de SQL Server Reporting Services 2017.

Il est par conséquent possible d’utiliser la récente API REST de Reporting Services pour réaliser certaines opérations via Script. Elle se trouve par défaut à cet emplacement : http://<urlserver>/reports/api/v2.0

La documentation exhaustive de l’API s’obtient à partir du fichier swagger. Elle a été mise en forme à cette adresse : https://app.swaggerhub.com/apis/microsoft-rs/pbirs/2.0

Les exemples sont ici fournis en PowerShell, mais un proxy .Net peut être obtenu à partir du fichier Swagger pour manipuler l’API au travers d’un modèle objet.

Le déploiement

L’API présente notamment une route PowerBIReports qui permet d’obtenir la liste des rapports PowerBI publiés si on la requête en GET.

Pour déployer via Script, la logique est d’appeler cette Url en POST avec la bonne structure.

Voici un extrait de code PowerShell permettant le déploiement d’un rapport depuis un dossier vers le serveur de rapport :

#Déclaration des variables
$ReportPortalUri = 'http://localhost/reports'
$uploadItemPath = 'D:\Repository\MonRapport.pbix'
$destName = 'MonRapportPublie'


#Liste des rapports existants
$powerBIReportsUri = $ReportPortalUri + "/api/v2.0/PowerBIReports"
$res = Invoke-WebRequest -Uri $powerBIReportsUri -Method Get -UseDefaultCredentials


#Upload du rapport (en POST - échouera s'il existe déja)
$bytes = [System.IO.File]::ReadAllBytes($uploadItemPath)
$payload = @{
"@odata.type" = "#Model.PowerBIReport";
"Content" = [System.Convert]::ToBase64String($bytes);
"ContentType"="";
"Name" = $destName;
"Path" = '/';
} | ConvertTo-Json
Invoke-WebRequest -Uri $powerBIReportsUri -Method Post -Body $payload -ContentType "application/json" -UseDefaultCredentials | Out-Null

La méthode POST créé le rapport s’il n’existe pas et retourne une erreur s’il existe déjà. Dans ce deuxième cas, la méthode PATCH devra être préférée puisqu’elle permet la mise à jour.

Le rapport apparait désormais sur le portail.

La configuration

A la suite de la publication, une opération fréquente consiste à modifier la configuration du rapport, une chaine de connexion par exemple. Il est également possible d’automatiser cette opération.

Les chaines de connexion d’un rapport sont exposées dans les DataSources de ce rapport. Cette collection est accessible dans l’API suivant cette route : PowerBIReports($id)/DataSources

Le script suivant permet d’obtenir la liste des sources de données d’un rapport, dont on a récupéré précédemment l’identifiant, et de modifier les credentials associés

#Déclaration des variables
$id = ""
$ReportPortalUri = 'http://localhost/reports'
$powerBIReportsDataSourceUri = $ReportPortalUri + "/api/v2.0/PowerBIReports($id)/DataSources"


#Récupération des sources de données
$res = Invoke-WebRequest -Uri $powerBIReportsDataSourceUri -Method Get -UseDefaultCredentials
$content =convertFrom-json ($res.Content)
$datasources = $content.value


#Affectation des credentials dans les objets DataSource
Foreach($dataSource in $datasources)
{
$dataModelDatasource = $dataSource.DataModelDataSource
$dataModelDatasource.Username = "Domain\user"
$dataModelDatasource.Secret = "Mdp"
}
#la serialisation Json par défaut ne traite les tableaux que sur 2 niveaux, il faut donc la forcer à au moins 3
$payload = convertTo-Json -depth 3($datasources)


#Modification des datasources sur le serveur de rapport
Invoke-WebRequest -Uri $powerBIReportsDataSourceUri -Method PATCH -Body $payload -ContentType "application/json" -UseDefaultCredentials

Les informations d’authentification sont désormais modifiées.

A suivre : La création de la planification du rafraîchissement des rapports ainsi déployés

PowerBI : Utiliser un segment pour choisir la mesure à observer dans un graphique

Un cas client qui revient de temps à autre : disposer d’un rapport PowerBi dans lequel un segment permet de changer la mesure observée dans la page :

 

L’implémentation proposée ici repose sur un tout petit peu de DAX et une table statique dans le modèle.  A noter que cette implémentation est également valable pour Excel 2016 avec PowerPivot ou un modèle SSAS Tabulaire.

Ajout d’une table statique au modèle

Partons d’un rapport simple contenant une seule table « ventes » à analyser.

Dans cette table, deux mesures pour notre exemple :

  • Nombre de commandes = COUNTROWS(Ventes)
  • Nombre de produits commandés = Sum(Ventes[Quantité])

Que nous souhaitons observer tour à tour dans le même visuel.

Nous allons ajouter une table « Mesure observée » au modèle via la fonction « Entrer des données » qui servira de support au segment.

L’astuce DAX

Nous allons ajouter une mesure dans la table « Mesure observée » intitulée valeur :

Valeur = If(HASONEVALUE('Mesure observée'[Mesure]);SWITCH(VALUES('Mesure observée'[Mesure]);"Quantité commandée";[Nombre de produits commandés];"Nombre de commande";[Nombre de commandes]);[Nombre de commandes])

Le HasOneValue permet de déterminer si une seule valeur a été sélectionnée dans le segment ou non. Si tel est le cas, suivant la valeur sélectionnée (fonction Switch), nous afficherons la mesure correspondante. Dans le cas contraire, nous utiliserons une mesure par défaut, ici, Nombre de commande.

La configuration du visuel

Dans la partie rapport, nous allons déposer un segment qui présente la colonne Mesure de la table Mesure observée pour permettre la sélection de la mesure.

Et des visuels, qui vont tous présenter la mesure valeur de la table Mesure observée plutôt que directement les mesures de la table vente.

Et le tour est joué, suivant le libellé de mesure sélectionné dans le segment, la mesure observée est différente 🙂

 

Build VSTS et migration EntityFramework Core

Les migrations EF Core c’est bien… dans Visual Studio

Les migrations Entity Framework rendent assez simple le versionning des bases de données applicatives et permettent de garantir l’alignement d’une modèle de données avec une version de l’application qui l’exploite.
La mise à jour de la base de données se fait alors en appelant le snippet powershell update-database depuis le package manager de Visual Studio dès lors que les outils EF Core sont installés dans celui-ci.
Là où le problème arrive, c’est lorsqu’on souhaite utiliser ce mécanisme dans le cadre d’un déploiement continu, particulièrement sur VSTS avec un hosted agent 2017.

En effet, tant que les migrations EntityFrameworkCore ne font l’objet d’aucune tâche dédiée dans VSTS, nous ne disposons pas d’outillage pour exécuter ces update-database.
Une option est de forcer la mise à jour de la base lors du démarrage de l’application (dans le statup.cs). Cette solution n’est pas satisfaisante si on souhaite que le mécanisme de Release nous prévienne d’un éventuel soucis de migration puisque ce serait la première requête http qui déclencherait la mise à jour de la base (alors que la Release serait déjà validée sans condition).
Voici un petit bricolage pour les faire fonctionner sur un Hosted agent 2017 :

Mettre à disposition de l’agent de build les outils EF core

Considérons que nous disposons déjà d’une migration dans un projet de model MyService.Models et d’un projet Web MyService.Web qui contient la chaîne de connexion dans ses appsettings.

En observant le fonctionnement de VS lors d’un Update-database (en activant le verbose avec l’option -v), on constate que la formule magique à exécuter s’appuie en fait sur EF.dll.
Cette assembly descend avec le package nugget microsoft.entityframeworkcore.tools
Pour appliquer les mises à jour de base de données, le contenu des packages microsoft.entityframeworkcore.design et microsoft.entityframeworkcore est également nécessaire.
Ce qui tout mis bout à bout produit une commande du type :

dotnet.exe exec
--depsfile C:\Source\Workspaces\MyApp\MyService\Main\MyService.Web\bin\Debug\netcoreapp2.0\MyService.Web.deps.json
--additionalprobingpath C:\.nuget\packages
--runtimeconfig C:\Source\Workspaces\MyApp\MyService\Main\MyService.Web\bin\Debug\netcoreapp2.0\MyService.Web.runtimeconfig.json
"C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.entityframeworkcore.tools\2.0.1\tools\netcoreapp2.0\ef.dll" database update
--context MediaContext
--verbose
--no-color
--prefix-output
--assembly C:\Source\Workspaces\MyApp\MyService\Main\MyService.Web\bin\Debug\netcoreapp2.0\MyService.Models.dll
--startup-assembly C:\Source\Workspaces\MyApp\MyService\Main\MyService.Web\bin\Debug\netcoreapp2.0\MyService.Web.dll
--project-dir C:\Source\Workspaces\MyApp\MyService\Main\MyService.Models\
--root-namespace MyService.Models

Notons que pour réaliser cette tâche, Visual Studio s’appuie une dll EF.dll, sur différents fichiers résultat de la compilation et fournit des configurations nécessaires.

Nous y reviendrons plus tard, dans l’immédiat, il faut mettre à disposition de l’agent de build Ef.dll et ses dépendances.
Le hosted agent ne partageant pas facilement ses packages nugget mutualisés (ce qui est le cas de ceux qui nous intéressent), le plus simple est de créer un dossier dans le projet MyService.Models intitulé EF-Build contenant ce dont nous avons besoin :

  • ef.dll (à trouver dans le dossier du package microsoft.entityframeworkcore.tools)
  • ef.runtimeconfig.json (package microsoft.entityframeworkcore.tools)
  • Microsoft.EntityFrameworkCore.Design.dll (package microsoft.entityframeworkcore.design)
  • Microsoft.EntityFrameworkCore.Design.xml (package microsoft.entityframeworkcore.design)
  • Microsoft.EntityFrameworkCore.dll (package microsoft.entityframeworkcore)
  • Microsoft.EntityFrameworkCore.xml (package microsoft.entityframeworkcore)

 

Archivons ça dans le contrôleur de source. Attention à bien archiver les dll qui par défaut sont exclues.

En aparté, si vous utilisez un agent de build privé, vous pouvez vous passer de cette étape et de la suivante et aller directement référencer les dll dans le repository de package de votre agent dans la section Mettre à jour la base de donnée lors de la release  de cet article.

Propager les outils EF Core pendant le build jusqu’à la Release

Les outils n’ont pas vocation à être utilisés pendant le build, mais lors de la release. Il faut cependant les ajouter aux artefacts du build.

Depuis VSTS, considérons un build sur le modèle de build pour Asp.net core et ajoutons une étape pour propager les outils jusqu’à la release :

Mettre à jour la base de donnée lors de la release

Trois étapes sont nécessaires lors de la release pour mettre à jour la base de données :

Extraire les outils Ef Core

Nous allons commencer par ajouter une tâche d’extraction des outils et accessoirement du résultat de la compilation lui-même puisqu’il contient les assemblies MyService.Model et MyService.Web :

Générer un script SQL et mise à jour de la base de données

L’étape clé, celle qui consiste à exécuter Ef.dll dans une configuration correcte, nous amène à reprendre ce qui se passe dans Visual Studio (retour au début de l’article si besoin). Il aurait été formidable de le faire en une seule tâche, mais n’ayant pas trouver de solution satisfaisante pour donner les droits à mon agent de Release de toucher le serveur SQL Azure dans une tâche ligne de commande, nous allons d’abord créer un script Update.sql idempotent avec une tâche d’exécution de ligne de commande :

NB : Un script IdemPotent (le -i en option ci-dessus) est un script SQL de mise à jour qui consulte la table __EFMigrationsHistory pour appliquer les modifications en tentant compte de la version de la base de données de destination.

Puis Exécuter le script en question au travers d’une tâche Execute Azure SQL Task (je passe ici sur la configuration de la base de donnée cible pour ne conserver que la configuration liée au script ):

Plus qu’à configurer l’exécution de la release et la base suivra (ou précédera suivant les goûts) 😉

Power BI : Création une table à partir d’une non-équi-jointure (utilisation d’un produit cartésien intermédiaire)

Lors de la création d’un P.O.C. Power BI pour un client j’ai été confronté au besoin de réaliser une non-équi-jointure pour alimenter une table à partir de deux onglets Excel. La solution adoptée n’étant pas immédiate, je vous la soumets.

Cas d’utilisation :

Création d’un calendrier prévisionnel pour une liste de contrats fournis dans un onglet Excel sur la base d’un calendrier fourni dans un autre document Excel.

Fichier Contrat

Nom Date Début contrat Date Fin contrat
Adam 01/04/2015 31/01/2016
Bob 01/06/2015 29/02/2016
Joe 01/01/2016

Fichier Calendrier

Mois Nb Jours ouvrés
01/01/2016 22
01/02/2016 20
01/03/2016 23

Première étape : Créer une table dans le dataset pour le fichier calendrier

Table calendrier

Deuxième étape : Produit cartésien pour obtenir une ligne par mois et par personne

Nous importons désormais le fichier Contrats.
Dans le requête permettant l’alimentation de la table Contrats, nous ajoutons une colonne de clé fictive qui va nous permettre de réaliser le produit cartésien. Cette clé fictive aura la valeur 1 et sera présente dans les deux jeux de données à lier. Dans la table contrats :
Cle Fictive Contrat
Nous faisons de même dans la requête d’import de la table Calendrier
Cle Fictive Calendrier
Nous utilisons ici la jointure interne basée sur la clé fictive (fusion de requêtes) de manière à ce que chaque ligne de la table Calendrier joigne avec chaque ligne de la table Contrat.
Fusion
Nous développons ensuite la table jointe pour en conserver les colonnes pertinentes « Mois » et « Nb Jours ouvrés ».
Développement de la table jointe
Nous avons réalisé un produit cartésien !

Le code M de la requête :

let
    Source = Excel.Workbook(File.Contents("source.xlsx"), null, true),
    Contrats_Sheet = Source{[Item="Contrats",Kind="Sheet"]}[Data],
    #"En-têtes promus" = Table.PromoteHeaders(Contrats_Sheet),
    #"Type modifié" = Table.TransformColumnTypes(#"En-têtes promus",{{"Nom", type text}, {"Début", type date}, {"Fin", type date}}),
    #"Personnalisée ajoutée" = Table.AddColumn(#"Type modifié", "CleFictive", each 1),
    #"Requêtes fusionnées" = Table.NestedJoin(#"Personnalisée ajoutée",{"CleFictive"},Calendrier,{"CleFictive"},"NewColumn",JoinKind.Inner),
    #"NewColumn développé" = Table.ExpandTableColumn(#"Requêtes fusionnées", "NewColumn", {"Période", "NbJours"}, {"NewColumn.Période", "NewColumn.NbJours"})
in
    #"NewColumn développé"

Troisième étape : Filtrer le résultat de la deuxième étape pour limiter à la période comprise dans le contrat

Nous utilisons ici la fonction Table.SelectRows pour ne conserver que les lignes comprises dans l’intervalle du contrat.
La condition de jointure est : debut ≤ periode && (fin ≥ periode or fin is null)
L’utilisation de cette fonction n’est pas associée à ma connaissance à un designer, nous allons donc éditer la requête et introduire une sélection de ligne juste avant le « in » :

let
    Source = Excel.Workbook(File.Contents("source.xlsx"), null, true),
    Contrats_Sheet = Source{[Item="Contrats",Kind="Sheet"]}[Data],
    #"En-têtes promus" = Table.PromoteHeaders(Contrats_Sheet),
    #"Type modifié" = Table.TransformColumnTypes(#"En-têtes promus",{{"Nom", type text}, {"Début", type date}, {"Fin", type date}}),
    #"Personnalisée ajoutée" = Table.AddColumn(#"Type modifié", "CleFictive", each 1),
    #"Requêtes fusionnées" = Table.NestedJoin(#"Personnalisée ajoutée",{"CleFictive"},Calendrier,{"CleFictive"},"NewColumn",JoinKind.Inner),
    #"NewColumn développé" = Table.ExpandTableColumn(#"Requêtes fusionnées", "NewColumn", {"Période", "NbJours"}, {"NewColumn.Période", "NewColumn.NbJours"}),
    #"periodes sélectionnées" = Table.SelectRows(#"NewColumn développé", each ([Début] = [NewColumn.Période] or [Fin] = null)))
in
    #"periodes sélectionnées"


NB : Exploitant un produit cartésien intermédiaire, il faut être sûr d’en maîtriser la volumétrie avant d’appliquer cette méthode au risque d’obtenir des temps de chargement du dataset exponentiels.
Bien entendu cette méthode n’a d’intérêt que si le traitement ne peut être réalisé sur la source comme c’est le cas ici.

SSAS : Modifier le Dimension Usage d’un cube en AMO (C#)

Dans le cadre d’un projet client, j’ai besoin de reconfigurer les relations entre les dimensions et les groupes de mesure de mon cube de manière dynamique.
Un utilisateur sélectionne un ensemble de dimension à « supprimer » des relations avec un groupe de mesure pour alléger le temps de process et le cube lui-même.

Voici donc une solution pour venir modifier le dimension usage en C# avant le process du cube.
L’exécution de cette routine sera assurée par un package SSIS qui fait déjà parti de la chaine d’alimentation de mon DataMart. Il s’agit ici du dernier package SSIS, celui qui sera également chargé du traitement du cube au travers d’une requête XMLA.

Référencer le namespace « Microsoft.AnalysisServices »

Les opérations disponibles en AMO sont exposées dans le Namespace « Microsoft.AnalysisServices » qui est embarqué dans l’assembly « Microsoft.AnalysisServices.dll ».
Cette assembly est mise à jour avec chaque version de SQL Serveur. Il est donc nécessaire de référencer la version de l’assembly compatible avec le serveur SSAS sur lequel les modifications sont à répercutées.
Traditionnellement, l’assembly se trouve dans le dossier : « C:\Program Files (x86)\Microsoft SQL Server\110\SDK\Assemblies ».
Ici le 110 correspondant à la version 2012 de SQL Server.
Une fois l’assembly référencée, le code du script débute donc avec le using du namespace :

using Microsoft.AnalysisServices;

Etablir une connexion au cube

Se connecter au cube à modifier revient à :

  • Etablir une connexion au serveur

    string strStringConnection = "Provider=MSOLAP.4;Data Source=localhost;Integrated Security=SSPI;Initial Catalog=MyDataBase;Impersonation Level=Impersonate";
    Server svr = new Server() ;
    svr.Connect(strStringConnection);
    

  • Etablir une connexion à la base

    String databaseName = "MyDataBase";
    Database db = svr.Databases.FindByName(databaseName);
    

  • Localiser le cube

    Cube cube = db.Cubes.FindByName("MyCube");
    

Exemple 1 : Supprimer la relation entre la dimension et le groupe de mesures

Les étapes suivantes indiquent comment supprimer la relation entre la dimension « Temps » et le groupes de mesures « Ventes »

  • Localiser le groupe de mesure

    MeasureGroup mgVente = cube.MeasureGroups.FindByName("Vente");
    

  • Localiser la relation entre le groupe de mesure « Ventes » et la dimension « Temps »

    RegularMeasureGroupDimension rmgVenteTemps = (RegularMeasureGroupDimension) mgVente.Dimensions["Temps"];
    

  • Supprimer la relation de la collection

    mgVente.Dimensions.Remove(rmgVenteTemps);
    

Exemple 2 : Supprimer la dimension du cube

Les étapes suivantes indiquent comment supprimer la dimension « Geography » du cube:

  • Localiser la dimension au niveau du cube

    CubeDimension dimension = cube.Dimensions.FindByName("Geography");
    

  • Supprimer la dimension de la collection

    cube.Dimensions.Remove(dimension);
    

Sauvegarde des modifications au cube

Pour imputer les modifications réalisées, l’appel à la méthode Update est nécessaire :

cube.Update(UpdateOptions.ExpandFull);


Un traitement du cube sera ensuite nécessaire.

Script complet de l’exemple 2 avec la gestion d’erreurs :

using System;
using System.Data;
using Microsoft.AnalysisServices;
public void Main()
        {
            using (Server svr = new Server())
            {
                string strStringConnection = "Provider=MSOLAP.4;Data Source=localhost;Integrated Security=SSPI;Initial Catalog=MyDataBase;Impersonation Level=Impersonate";

                try
                {
                    svr.Connect(strStringConnection);
                }
                #region ErrorHandling
                catch (AmoException e)
                {
                    Dts.Events.FireError(-1, "Amo operation to manipulate dimension Usage", "Unable to connect the server : " + e.Message, "", -1);
                    Dts.TaskResult = (int)ScriptResults.Failure;
                    return;
                }
                #endregion

                String databaseName = "MyDataBase";
                Database db = svr.Databases.FindByName(databaseName);
                if (db == null)
                {
                    Dts.Events.FireError(-1, "Amo operation to manipulate dimension Usage", "Unable to connect the database : " + databaseName, "", -1);
                    Dts.TaskResult = (int)ScriptResults.Failure;
                    return;
                }

                string cubeName = "MyCube";
                Cube cube = db.Cubes.FindByName(cubeName);

                if (cube == null)
                {
                    Dts.Events.FireError(-1, "Amo operation to manipulate dimension Usage", "Unable to find the cube : " + cubeName, "", -1);
                    Dts.TaskResult = (int)ScriptResults.Failure;
                    return;
                }
                string dimensionName = "Geography";
                CubeDimension dimension = cube.Dimensions.FindByName(dimensionName);
                cube.Dimensions.Remove(dimension);

                cube.Update(UpdateOptions.ExpandFull);
            }
            Dts.TaskResult = (int)ScriptResults.Success;
  }
}

Variables dans un projet de Base de données Visual studio – Sql Server Data Tools et SQLCmd Variables

Les projets de bases de données permettent entre autres bonnes idées de disposer d’un suivi de version sur les structures de base de données SQL Server.
Ce type de projet a évolué depuis plusieurs versions de Visual Studio. La solution présentée ici concerne Visual Studio 2013 avec SSDT Database pour SQL Server 2012, cependant le principe était valable avec les précédentes versions de SSDT et avec la version 2014 de SQL Server.

Déclaration des variables et utilisation dans les scripts

Dans ce type de projet, il est possible de joindre à différentes occasions des scripts SQL pour

  • Définir une fonction ou procédure stockée
  • Définir un filegroup, une fonction de partitionnement ou un schéma de partitionnement
  • Insérer des données via un postbuild script

Ces projets sont implicitement destinés à maintenir plusieurs environnements (Développement, Intégration, Production). Au sein de ces scripts, nous pouvons avoir besoin de rendre dynamique une section de code (le lecteur pour un FileGroup, le préfixe pour un nom de table…)
L’aspect dynamique de ces scripts SQL peut être piloté grâce à des variables SQLCmd.
Pour utiliser de telle variable dans les scripts sql de notre projet, la syntaxe est $(NomDeVariable)

Il existe 4 variables prédéfinies pour les projets de base de données :

  • DatabaseName
  • DefaultDataPath
  • DefaultFilePrefix
  • DefaultLogPath

Code sql avec une variable système : Script d’un FileGroup dans le projet Database

ALTER DATABASE [$(DatabaseName)]
ADD FILEGROUP [TestFG];

Si j’ai besoin de variables supplémentaires, par exemple pour distribuer sur 2 disques les filegroups de ma base de données, la procédure est la suivante :

1. Déclaration dans les propriétés du projet

Sur l’onglet SQLCMD Variables, déclarez les variables supplémentaires, ici « DataPathEven » et « DataPathOdd »
Fenêtre de propriétés du projet

2. Utilisation depuis les scripts

Ces variables peuvent ensuite être utilisées dans un script SQL avec la syntaxe permettant d’accéder aux variables. J’ajoute ici un nouveau FileGroup dans le répertoire Storage de mon projet de base de données.

Code sql avec une variable utilisateur : Script d’un FileGroup dans le projet Database

ALTER DATABASE [$(DatabaseName)]
ADD FilE ( NAME = N'TestFG1', FILENAME = N'$(DataPathOdd)TestFG1.ndf' , SIZE = 0KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
GO
ALTER DATABASE [$(DatabaseName)]
ADD FilE ( NAME = N'TestFG2', FILENAME = N'$(DataPathEven)TestFG2.ndf' , SIZE = 0KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )

Déploiement depuis SSDT

Depuis SSDT, la publication du projet permet d’affecter, via un designer des valeurs aux variables SQLCmd utilisateur.
L’interface ci-dessous est alors disponible.
Affectation de valeurs aux variables dans la publication depuis SSDT
Ici, les valeurs « D:\Data\Odd\ » et « D:\Data\Even » viennent surcharger les valeurs configurées à la déclaration des variables pour la publication en cours.
Cette publication peut être sauvegardée sous forme de profile de publication dans le projet de manière à être réutilisée plus tard.
Les valeurs des variables SQLCmd sont alors embarquées dans le profil de publication.
Le xml ainsi généré contient dans mon cas la section suivante

<ItemGroup>
<SqlCmdVariable Include="DataPathEven">
<Value>D:\Data\Odd\</Value>
</SqlCmdVariable>
<SqlCmdVariable Include="DataPathOdd">
<Value>D:\Data\Even\</Value>
</SqlCmdVariable>
</ItemGroup>

Déploiement du projet en dehors de SSDT

Déploiement depuis le SQL Server Management Studio

Depuis le Management Studio de SQL Server, l’assistant de mise à jour de data-tiers application ne permettra plus le déploiement. En effet, cet assistant n’est prévu ni pour utiliser un profil de publication, ni pour configurer les SQLCmd Variables du projet. Or, la configuration de ces variables étant nécessaire au déploiement, celui-ci échoue avec le message :
Erreur lors du déploiement depuis SSMS
On abandonnera donc cette solution pour se tourner vers un déploiement scripté.
Plusieurs possibilités sont envisageables pour atteindre cet objectif dont:
• Utiliser l’utilitaire en ligne de commande SQLPackage et configurer au déploiement les valeurs à affecter aux variables ou réutiliser un profil de publication
• Utiliser des tâches de déploiement MSBuild et les profils de publication.
La seconde solution fera l’objet d’un article dans quelques temps, je ne la développe pas ici.

Déploiement avec SQLPackage et affectation de valeur aux SQLCmd Variables

Pour la solution reposant sur SQLPackage, comme nous l’indique MSDN, nous pouvons affecter via la ligne de commandes des valeurs aux SQLCmd variable lors de notre déploiement.
Sur mon installation, l’exécutable SQLPackage peut-être trouvé à l’emplacement (à adapter suivant votre version de SQL Server) :
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120
Dans mon cas, le déploiement pourrait être réalisé en utilisant une commande de la forme suivante (exécutée dans le répertoire bin\debug de mon projet de base de données) :

SQLPackage /Action:Publish /SourceFile:DbProject.dacpac /TargetServerName:MyHost / TargetDatabaseName:DbProject

Sous cette forme, le déploiement échoue m’indiquant que les valeurs des SQLCmd variables ne sont pas fournies. Le paramètre « Variables » nous permettra d’affecter des valeurs à nos variables.

SQLPackage /Action:Publish /SourceFile:DbProject.dacpac /TargetServerName:MyHost / TargetDatabaseName:DbProject /Variables:DataPathOdd=D:\Data\Odd /Variables:DataPathEven=D:\Data\Even

Déploiement avec SQLPackage et utilisation d’un profil de publication

Nous pouvons également réutiliser un profil de publication en indiquant son chemin avec la syntaxe :

SQLPackage /Action:Publish /SourceFile:DbProject.dacpac /TargetServerName:MyHost / TargetDatabaseName:DbProject /Profile:{ProjectPath}DbProject.publish.xml