<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>BI Grab Bag</title>
	<atom:link href="http://agilebi.com/ddarden/feed/" rel="self" type="application/rss+xml" />
	<link>http://agilebi.com/ddarden</link>
	<description>A random assortment of BI tips, tricks, and information</description>
	<lastBuildDate>Sun, 16 Oct 2011 18:16:58 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Deploying a custom SSRS Assembly to the GAC</title>
		<link>http://agilebi.com/ddarden/2011/10/16/deploying-a-custom-ssrs-assembly-to-the-gac/</link>
		<comments>http://agilebi.com/ddarden/2011/10/16/deploying-a-custom-ssrs-assembly-to-the-gac/#comments</comments>
		<pubDate>Sun, 16 Oct 2011 18:16:09 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[How To]]></category>
		<category><![CDATA[ssrs automation]]></category>

		<guid isPermaLink="false">http://agilebi.com/ddarden/?p=102</guid>
		<description><![CDATA[I briefly mentioned a technique I used to deploy custom assemblies for SSRS to the GAC in my PASS presentation on Building a Reporting Services Framework.&#160; I didn’t spend a lot of time on that, but I did get some questions on it, so I thought I’d go into more detail here.&#160; Here’s a pointer [...]]]></description>
			<content:encoded><![CDATA[<p>I briefly mentioned a technique I used to deploy custom assemblies for SSRS to the GAC in my PASS presentation on </p>
<p>Building a Reporting Services Framework.&#160; I didn’t spend a lot of time on that, but I did get some questions on it, so I thought I’d go into more detail here.&#160; Here’s a pointer to the <a href="http://msdn.microsoft.com/en-us/library/ms155034.aspx" target="_blank">documentation around custom assembly deployment</a> from MS.&#160; This post is how to automate the process if you’re using the GAC method.</p>
<p>Here is a <a href="https://skydrive.live.com/?cid=0deda612a5d5d66e&amp;sc=documents&amp;uc=1&amp;id=DEDA612A5D5D66E%21512#" target="_blank">code sample</a> showing how I perform the remote deployment.</p>
<h1>Local Deployments</h1>
<p>Before we actually get around to deploying to the server we need to be able to test our custom assembly locally.&#160; I use the following Post-Build Event in my custom assembly.&#160; If you aren’t familiar with Build Events, you get to them by right-clicking on the project and choosing ‘Build Events’ in the left hand side of the properties.</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: bash; pad-line-numbers: true; title: ; notranslate">
copy &quot;$(TargetPath)&quot; &quot;%ProgramFiles%\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies&quot;

gacutil.exe  -i $(TargetPath)

copy /Y &quot;$(TargetPath)&quot; &quot;C:\_PASS_Demo\Deploy\Assemblies&quot;
</pre>
</pre>
</div>
<p>This build event does three things:</p>
<ol>
<li>Copies the dll to the PrivateAssemblies directory.&#160; This makes the custom assembly available to BIDS so you can use it in Preview Mode.&#160; You have to restart BIDS after updating the custom assembly for the new version to be loaded. </li>
<li>GAC’s the custom assembly so it can be used by your local Reporting Services instance.&#160; This will require a restart of the service to be live, but I typically don’t include that in the build process because it takes a few seconds and I’m use it less often. </li>
<li>Copies the dll to the Deployment section of my solution.&#160; I typically source control the dll, and use the process described later in this post to deploy the assembly to other environments. </li>
</ol>
<h1>Remote Deployments</h1>
<p>Here is the solution that I found for automating deployments for other servers.&#160; You have to jump through a few hoops to be able to do this (at least with Windows Server 2008 R2).&#160; If you search the web you’ll find a few other solutions to this problem, but I didn’t find any that met my specific needs.&#160; The specific scenario I was trying to enable was unattended deployments.&#160; If you log in to the machine, or if you’re able to provide credentials, this is an easier problem.&#160; This was the only way I found to do this without manually collecting credentials.&#160; If you do that and use credssp, you can use PowerShell to do this remotely.&#160; If you are willing to send an admin password clear text, you can use PSExec.&#160; Neither of those two solutions met my needs, so I chose the following approach:</p>
<ol>
<li>Copy the assembly to the target machine.</li>
<li>Install the assembly using System.EnterpriseServices executed via a remote call.</li>
<li>Verify the assembly was actually installed (the installation procedure doesn’t return failures).</li>
<li>Restart the ReportServer service.</li>
</ol>
<p>You might need to tweak the example scripts a little bit for your environment.&#160; You might also want to dot source the DeploymentLibrary script (i.e., ./DeploymentLibrary.ps1) in deploy_ssrs_assemblies.ps1.&#160; I have all my libraries load as part of my profile, so this may assume the library is already loaded.&#160; The installation process just takes the path you deployed the assembly to (such as “<a href="//\\Server01-dev\d$\Reports\deployment\assemblies\EltUtilities.dll">\\Server01-dev\d$\Reports\deployment\assemblies\EltUtilities.dll</a>”) and figures out the name of the assembly and the server from it.</p>
<p>The sample I provided is laid out as follows:</p>
<ol>
<li>Deployment – The root of the deployment structure.</li>
<ol>
<li>Assemblies – The assemblies we want to deploy.&#160; I also typically include other libraries used for other types of automated deployments here as well.</li>
<li>Dev – This contains a simple driver file that sets all the variables necessary for a deployment to the dev environment.&#160; I usually also have a localhost, qa, and prod folder as well (identical scripts except for the variables).&#160; The code to set up the logging should probably be refactored into another method in the PowerShell library, but that’s on the TODO list.</li>
<li>deploy_ssrs_assemblies.ps1 – The script that handles the orchestration of the deployment.&#160; This is common, parameter driven functionality that is called by the drivers for each of the environments.</li>
<li>DeploymentLibrary.ps1 – The methods that install the assembly on the remote server and restart the services.</li>
</ol>
</ol>
<p>There are a few moving pieces to this process, but the example should be pretty well documented.&#160; </p>
<h1>Conclusion</h1>
<p>That’s about it.&#160; If this solution doesn’t meet your specific needs, there are a few other ones out there (such as <a href="http://remotegacutil.codeplex.com/" target="_blank">Remote GAC Manager</a>) that might, or you can just use the standard PSExec approach.&#160; Happy automating!</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2011/10/16/deploying-a-custom-ssrs-assembly-to-the-gac/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Demo Materials for PASS Session BIA-304&#8211;Building a Reporting Services Framework</title>
		<link>http://agilebi.com/ddarden/2011/10/16/demo-materials-for-pass-session-bia-304building-a-reporting-services-framework/</link>
		<comments>http://agilebi.com/ddarden/2011/10/16/demo-materials-for-pass-session-bia-304building-a-reporting-services-framework/#comments</comments>
		<pubDate>Sun, 16 Oct 2011 16:45:51 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[PASS SSRS]]></category>

		<guid isPermaLink="false">http://agilebi.com/ddarden/?p=96</guid>
		<description><![CDATA[I presented my session on ‘Building a Reporting Services Framework’ at the PASS Summit 2011 on Friday.&#160; I had a great time at the summit, both presenting and attending all the great sessions.&#160; A wonderful time was had by all. Here’s a link to the demo materials I went through.&#160; You probably don’t want to [...]]]></description>
			<content:encoded><![CDATA[<p>I presented my session on ‘Building a Reporting Services Framework’ at the PASS Summit 2011 on Friday.&#160; I had a great time at the summit, both presenting and attending all the great sessions.&#160; A wonderful time was had by all.</p>
<p>Here’s a link to the <a href="https://skydrive.live.com/?cid=0deda612a5d5d66e&amp;sc=documents&amp;id=DEDA612A5D5D66E%21511#" target="_blank">demo materials</a> I went through.&#160; You probably don’t want to just push any of this out to production as is, but it should be a good start.&#160; I also used the <a href="http://msftdbprodsamples.codeplex.com/" target="_blank">Adventure Works 2008 R2 database</a>, but it isn’t included in this package.</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2011/10/16/demo-materials-for-pass-session-bia-304building-a-reporting-services-framework/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Automatically Using the Latest Version of an SSRS Template</title>
		<link>http://agilebi.com/ddarden/2011/07/12/automatically-using-the-latest-version-of-an-ssrs-template/</link>
		<comments>http://agilebi.com/ddarden/2011/07/12/automatically-using-the-latest-version-of-an-ssrs-template/#comments</comments>
		<pubDate>Wed, 13 Jul 2011 03:46:33 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[ssrs]]></category>

		<guid isPermaLink="false">http://agilebi.com/ddarden/?p=92</guid>
		<description><![CDATA[The ability to add Templates to SSRS (such as documented here and here and here) is a neat feature.&#160; Basically, you just create an RDL and put it in the “C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\ProjectItems\ReportProject\” directory… now you can create a new report starting from the template by selecting it from the “New Items” [...]]]></description>
			<content:encoded><![CDATA[<p>The ability to add Templates to SSRS (such as documented <a href="http://weblogs.sqlteam.com/jhermiz/archive/2007/08/14/60283.aspx" target="_blank">here</a> and <a href="http://blogs.msdn.com/b/bimusings/archive/2005/12/06/500462.aspx" target="_blank">here</a> and <a href="http://www.simple-talk.com/sql/reporting-services/ten-common-sql-server-reporting-services-challenges-and-solutions/#hr9" target="_blank">here</a>) is a neat feature.&#160; Basically, you just create an RDL and put it in the “C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\ProjectItems\ReportProject\” directory… now you can create a new report starting from the template by selecting it from the “New Items” dialog.&#160; This is great when you don’t want to start from scratch on every report.</p>
<p>The only thing I’ve found to be a friction point is that every time someone updates the Template (which you’re storing somewhere in source control) you need to copy it over to that folder.&#160; That’s no big deal, but when you have a lot of developers that all have to do that frequently, it’s a pain.</p>
<p>The easy way to solve this is with symlinks.&#160; You can’t use a shortcut, but you can create a soft symlink to point back at your template in source control.&#160; That way you will always create a report off of the latest version in your repository.&#160; (Yeah, you still have to keep that up to date… no getting around that.)</p>
<p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: bash; pad-line-numbers: true; title: ; wrap-lines: true; notranslate">
mklink &quot;C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\ProjectItems\ReportProject\TemplatePortrait.rdl&quot;  &quot;C:\SourceControl\frameworks\SsrsFramework\ReportingTemplates\TemplatePortrait.rdl&quot;

mklink &quot;C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\ProjectItems\ReportProject\TemplateLandscape.rdl&quot; &quot;C:\SourceControl\frameworks\SsrsFramework\ReportingTemplates\TemplateLandscape.rdl&quot;
</pre>
</pre>
</div>
<p>That’s all there is to it!</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2011/07/12/automatically-using-the-latest-version-of-an-ssrs-template/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Using SSRS queries with parameters with Netezza</title>
		<link>http://agilebi.com/ddarden/2011/07/05/using-ssrs-queries-with-parameters-with-netezza/</link>
		<comments>http://agilebi.com/ddarden/2011/07/05/using-ssrs-queries-with-parameters-with-netezza/#comments</comments>
		<pubDate>Wed, 06 Jul 2011 06:00:52 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://agilebi.com/ddarden/?p=82</guid>
		<description><![CDATA[Our company has started using SQL Server Reporting Services 2008 R2 to handle most of our reporting against Netezza.&#160; This works pretty well over all using the Netezza 6.0 OLE DB driver.&#160; There are a few things to be aware of though. I found a number of people were using expressions to construct a SQL [...]]]></description>
			<content:encoded><![CDATA[<p>Our company has started using SQL Server Reporting Services 2008 R2 to handle most of our reporting against Netezza.&#160; This works pretty well over all using the Netezza 6.0 OLE DB driver.&#160; There are a few things to be aware of though.</p>
<p>I found a number of people were using expressions to construct a SQL statement to send for the data set.&#160; This works well, but it’s a little bit of a pain to both write and maintain.&#160; Luckily, we can use parameters (with just a few things to be aware of).&#160; That means we can write SQL that looks like this:</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: sql; pad-line-numbers: true; title: ; notranslate">
SELECT
     date_key
    ,date
    ,good_day
    ,day_of_week
FROM
    date_sample
WHERE
    1=1
    AND day_of_week IN (@day_of_week_multi)
    AND date IN (@date)
    AND date_key =  @date_key
    AND good_day = @good_day
ORDER BY
    date_key
;
</pre>
</pre>
</div>
<p>You can use parameters in the normal way with Netezza, but there are a few details to be aware of.&#160; You just imbed the parameter in the query like</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: sql; pad-line-numbers: true; title: ; notranslate">
WHERE cola = @param
</pre>
</pre>
</div>
<p>and create a corresponding parameter in your data sets.&#160; The OLE DB driver handle surrounding surrounding the parameter with quotes when required (strings and date times), and it automatically escapes any single quotes in the string.</p>
<p>Parameters are ordinal, so you need to create a parameter (in order) for each value you want to send. There parameters in your query will be replaced in order based on the parameters you specified in the dataset.&#160; No big deal, but something to be aware of.&#160; You can either use a either a normal parameter (such as <a href="mailto:&lsquo;@myparam&rsquo;">‘@myparam’</a>) or a question mark (‘?’).</p>
<p>Hears the rub… the OLE DB driver also automatically escapes single quotes so you don’t have to worry about that.&#160; This is great… but there is a little bit of an issue when you want to use multi-value parameters.&#160; Basically, the normal method of using SQL like</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: sql; title: ; notranslate">
WHERE col_a IN (@multi_value_param)
</pre>
</pre>
</div>
<p>and an expression like this for a parameter</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: vb; title: ; notranslate">
=JOIN(@multi_param, &quot;’,’”)
</pre>
</pre>
</div>
<p>doesn’t work because the single ticks will always be escaped (so you’ll see a value like “’val1’’,’’val2’’,’’val3’” instead of “’val1’,’val2’,’val3’” in the resulting SQL).&#160; No problem… we just need a different approach.&#160; We’ll use something in the WHERE clause like</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: sql; title: ; notranslate">
STRPOS(@multi_param, '|' || col_a|| '|') &gt; 0
</pre>
</pre>
</div>
<p>with an expression like</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: vb; title: ; notranslate">
=”|” + JOIN(@multi_param, &quot;|”) + “|”
</pre>
</pre>
</div>
<p>This will bracket each value in a special “end of column” character such as “|”, and thus look for an exact match.&#160; You may need to use a different character, or handle escaping, depending on your data.&#160; You can always use multiple characters if you’d like (such as “~~~” as a delimiter).&#160; This is a little hacky, but not too bad.&#160; Remember to add the delimiter to the start and end of the string as well so you can match those values as well.</p>
<p>I tested this on a 1.5 billion row table against a similar query that uses an IN clause.&#160; I found the two methods were essentially equal in performance, though that might degrade for an extremely large number of parameter values.&#160; These both provide exact matches against the value, and they provide equivalent results.</p>
<p>But… what about using a multi-select on a date?&#160; Unfortunately, that get’s slightly more complicated, but not too bad.&#160; All you need to do is write a little code such as</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: vb; pad-line-numbers: true; title: ; notranslate">
    ''' &lt;summary&gt;
    ''' Convert an array of Dates into a string.
    ''' &lt;/summary&gt;
    ''' &lt;param name=&quot;inputArray&quot;&gt;Array of dates to convert.&lt;/param&gt;
    ''' &lt;param name=&quot;delimiter&quot;&gt;Delimiter to place between each item in the array.&lt;/param&gt;
    ''' &lt;param name=&quot;formatString&quot;&gt;Format string for the date.&lt;/param&gt;
    ''' &lt;returns&gt;String of formatted datetimes separated by a delimiter.&lt;/returns&gt;
    ''' &lt;remarks&gt;&lt;/remarks&gt;
    Public Function JoinDate(ByVal inputArray() As Object, ByVal delimiter As String, ByVal formatString As String) As String
        Dim output As String = String.Empty

        Dim i As Integer
        For i = 0 To inputArray.Length - 1
            output += (CDate(inputArray(i))).ToString(formatString) + delimiter
        Next i

        ' Trim the trailing delimiter off the string
        If output.Length &gt; 0 Then
            output = output.Substring(0, output.Length - delimiter.Length)
        End If

        Return output

    End Function
</pre>
</pre>
</div>
<p>And use an expression like</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: vb; title: ; notranslate">
=&quot;|&quot; + Code.JoinDate(Parameters!date.Value, &quot;|&quot;, &quot;yyyy-MM-dd&quot;) + &quot;|&quot;
</pre>
</pre>
</div>
<p>And you’re good to go!&#160; This will convert a multi-select DateTime variable (which is an array of DateTime objects) into a string containing dates formatted as you request.</p>
<p>So for this example where we are constraining on a multi-value string parameter, a multi-value date parameter, plus a few other standard guys, we end up with</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: sql; title: ; notranslate">
SELECT
     date_key
    ,date
    ,good_day
    ,day_of_week
FROM
    date_sample
WHERE
    1=1
    AND strpos(@day_of_week_multi, '|' ||day_of_week || '|') &gt; 0
    AND strpos(@date, '|' || date || '|') &gt; 0
    AND date_key =  @date_key
    AND good_day = @good_day
ORDER BY
    date_key
;
</pre>
</pre>
</div>
<p><a href="https://skydrive.live.com/embedicon.aspx/Public/Blog/NetezzaParameters/SsrsMultiValueParameterIssue.zip?cid=0deda612a5d5d66e&amp;sc=documents" target="_blank">Here’s a sample</a> that shows how to use integer, datetime, and string values (with some examples of multi-value parameters thrown in).&#160; Just use the SQL script to build and populate the test table, set the data source appropriately, and play around with it.</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2011/07/05/using-ssrs-queries-with-parameters-with-netezza/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Extracting MySql UTF-8 fields with SSIS</title>
		<link>http://agilebi.com/ddarden/2010/09/19/extracting-mysql-utf-8-fields-with-ssis/</link>
		<comments>http://agilebi.com/ddarden/2010/09/19/extracting-mysql-utf-8-fields-with-ssis/#comments</comments>
		<pubDate>Sun, 19 Sep 2010 23:51:16 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://agilebi.com/ddarden/?p=63</guid>
		<description><![CDATA[Last week I ran into an interesting task with SSIS.&#160; I’m pulling data from a MySql database (5.0.79-enterprise) using the MySql ADO.Net Connector with SSIS and loading the data into a SQL Server 2008 R2 database.&#160; This has worked pretty well, but I ran into a few issues when dealing with UTF-8, so I thought [...]]]></description>
			<content:encoded><![CDATA[<p>Last week I ran into an interesting task with SSIS.&#160; I’m pulling data from a MySql database (5.0.79-enterprise) using the <a href="http://dev.mysql.com/downloads/connector/net/">MySql ADO.Net Connector</a> with SSIS and loading the data into a SQL Server 2008 R2 database.&#160; This has worked pretty well, but I ran into a few issues when dealing with UTF-8, so I thought I’d document some of my steps here.</p>
<p>Before we dive into the code, here’s a little information on how UTF-8 is handled in MySql and SQL Server in case you’re not familar with it.&#160; Here’s <a href="http://en.wikipedia.org/wiki/Unicode">some more</a> information on Unicode that’s good reading material as well.</p>
<p>MySql doesn’t have the concept of national data types (such as nvarchar or nchar).&#160; This is handled by setting the CHARSET of each table (which can be latin1, UTF-8, etc.).&#160; You can read more about how MySql handles unicode characters <a href="http://dev.mysql.com/doc/refman/5.1/en/charset-unicode.html">in the MySql documentation</a>.&#160; One thing to note is that MySql support UTF-8 and UCS2, but you need to be aware of a few details about the implementation… The UTF-8 implementation does not use a BOM, and the UCS-2 implementation uses big-endian byte order and does not use a BOM.&#160; The ADO.Net Connector doesn’t allow you to set a Code Page when you source the data.</p>
<p>SQL Server stores unicode data in national fields (nchar, nvarchar) using a little-endian UCS-2 encoding.&#160; See <a href="http://msdn.microsoft.com/en-us/library/bb330962(SQL.90).aspx">http://msdn.microsoft.com/en-us/library/bb330962(SQL.90).aspx</a> for more details on this (it was written for 2005, but is applicable to 2008 R2 as well). UCS-2 is a predecessor of UTF-16. UCS-2 differs from UTF-16 in that UCS-2 is a fixed-length encoding that represents all characters as a 16-bit value (2 bytes), and therefore does not support supplementary characters. UCS-2 is frequently confused with UTF-16, which is used to internally represent text in the Microsoft Windows operating systems (Windows NT, Windows 2000, Windows XP, and Windows CE), but UCS-2 is more limited.&#160; You may note that the UCS-2 implementations between the two systems are different, so you will have to transform the strings when transferring data between the two systems.</p>
<p>Our source system has been around for quite awhile… it started off only supporting latin character sets, but as the company grew we had to handle international characters as well.&#160; Some tables were created using a UTF-8 character set, but some were never converted from latin1… the front end just started inserting UTF-8 strings into the fields.&#160; This means that in certain cases, we have different encodings in the same field which have to be handled.&#160; This doesn’t materially affect the details of how I implemented this solution, but it does mean that some of the built-in conversion function in MySql won’t necessarily behave as expected, and that you sometimes have to handle a field differently based on when it was stored in the database.</p>
<h1>Getting Started</h1>
<p>So how do you even know you have a problem like this?&#160; The issue is how the data is represented in each system.&#160;&#160; I was trying to get a consistent representation of my data across multiple systems in Linux and Windows, and through a variety of client tools.&#160; Particularly if you don’t control the source of your data, you need to determine if it is correct, if there is an encoding issue, or if there is just a display issue.&#160; One thing that is important is to make sure your tools can actually handle displaying these characters… some can’t.&#160; When in doubt, I’d always fall back to something like <a href="http://notepad-plus-plus.org/">Notepad++</a> with A <a href="http://sourceforge.net/projects/npp-plugins/files/">Hex Editor</a> plug-in.&#160; Here’s an example of a word expressed in hex (which is correct in the source system), the word as it is displayed in the source system (where it was incorrectly encoded), and the word as it should be expressed when encoded correctly. </p>
<p><a href="http://agilebi.com/ddarden/files/2010/09/clip_image001.png"><img style="border-right-width: 0px;border-top-width: 0px;border-bottom-width: 0px;border-left-width: 0px" border="0" alt="clip_image001" src="http://agilebi.com/ddarden/files/2010/09/clip_image001_thumb.png" width="244" height="107" /></a></p>
<p>When I was initially looking at the data, I tended to focus on a few rows/columns where I could easily see the data was incorrect.&#160; I found Japanese and German to be the easiest for this… Japanese tends to display as “all or nothing” being correct, where as characters such as umlauts in German will be displayed differently in each encoding, giving you a good clue when things are right and wrong.&#160; I find I used a lot of functions such as “HEX()” in MySql and “CAST(xxx AS varbinary)” in SQL Server to look at the hex representations, and I will often dump query results to a text file and look at it in Notepad++ to verify what I see.</p>
<h1>The Approach</h1>
<p>I’m pulling data in from the source system directly, not landing it in a file before loading it in.&#160; That approach can be used to avoid some of these issues… depending on how the data is stored in the source system, you could just create a Flat File source and import that data using a Code Page of 65001 to transform the Unicode characters.&#160; In my particular situation, because of how the data was stored, this wasn’t possible… even if I did want to land the data multiple times, which I didn’t.</p>
<p>To start, I created an ADO.Net source to pull data from the source.&#160; All the fields from the source are typed as WSTR… but they still have to be translated from UTF-8 to Windows Unicode.&#160; The most reliable way I found to do this was to create a query like this:</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: sql; pad-line-numbers: true; title: ; notranslate">
SELECT
     id
    ,CAST(COALESCE(NULLIF(field1, ''), ' ') AS binary) AS field1_binary
    ,CAST(COALESCE(NULLIF(field2, ''), ' ') AS binary) AS field2_binary
FROM
    myTable
</pre>
</pre>
</div>
<p>The purpose of this is to convert the string to binary (so SSIS will see it as a BYTESTREAM).&#160; So why the the NULLIF and COALESCE, you’re probably asking?&#160; SSIS doesn’t like null byte streams… they cause an error (more on that later).&#160; Unfortunately, when you try and cast an empty string to a binary, it is transformed into a null.&#160; I haven’t found a way around that, and further haven’t found a way to COALESCE that null back into anything.&#160; It looks like once it becomes a null, it stays a null.&#160; The solution I found was to convert all nulls and empty strings to a single space, then convert that back to a null downstream.&#160; This isn’t optimal, but it works fine in my situation so I’m OK with it.</p>
<p>Once we get each of these strings inside SSIS as byte streams, we need to convert them from UTF-8 byte streams into Unicode byte streams.&#160; This isn’t difficult to do in C#, so we just need to create a Script Transform.&#160; You use the binary fields as inputs, then create WSTR outputs for field1 and field2.&#160; Then we use a method that looks like this:</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: csharp; title: ; notranslate">
public static string GetUnicodeFromUtf8ByteStream(byte[] input, bool convertEmptyStringToNull)
{
    // Create a UTF-8 string from the UTF-8 byte stream
    string inputAsUtf8 = System.Text.Encoding.UTF8.GetString(input, 0, input.Length); 

    // Opportunity to short-circuit; if the string is empty, and
    // the user wants to return nulls for empty strings, go ahead
    // and return a null.
    if (convertEmptyStringToNull &amp;amp;&amp;amp; inputAsUtf8.Trim().Length == 0)
    {
        return null;
    } 

    // Convert the  UTF-8 encoded string into a Unicode byte stream
    byte[] convertedToUnicode = System.Text.Encoding.Unicode.GetBytes(inputAsUtf8);
    // Convert the Unicode byte stream into a unicode string
    string output = System.Text.Encoding.Unicode.GetString(convertedToUnicode); 

    // Return the correctly encoded string
    return output;
}
</pre>
</pre>
</div>
<p>I also created a separate method</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: csharp; title: ; notranslate">
public static string GetUnicodeFromUtf8ByteStream(byte[] input)
{
    return GetUnicodeFromUtf8ByteStream(input, true);
}
</pre>
</pre>
</div>
<p>To provide default behavior on how to handle empty strings.&#160; I used this to work around the issue where empty strings don’t come across from MySql.&#160; If you have to differentiate between nulls and empty strings, you’ll need to come up with a work around.</p>
<p>You could also probably just use the method System.Text.Encoding.Convert(Encoding srcEncoding, Encoding dstEncoding, byte[] bytes), but I wanted more control over the transformation.&#160; I haven’t tested that, but it should work.</p>
<p>I have these methods (along with another few transforms) in a custom assembly, but you can put this directly into the transformation component.&#160; Then, you just need code like this in your ProcessInputRow method:</p>
<div style="padding-bottom: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;float: none;padding-top: 0px" class="wlWriterEditableSmartContent">
<pre>
<pre class="brush: csharp; title: ; notranslate">
// Convert fields that are stored in UTF-8 format into Unicode
Row.field1 = Converter.GetUnicodeFromUtf8ByteStream(Row.field1binary);
Row.field2 = Converter.GetUnicodeFromUtf8ByteStream(Row.field2binary);
</pre>
</pre>
</div>
<p>This converts a field containing UTF-8 data into a proper Unicode string inside of SQL Server.</p>
<h1>The Performance</h1>
<p>Of course, any time you do something like this there is the question of performance.&#160; I initially ran this test on a set with about 3.5 million records, with 6 fields I was performing the conversion on.&#160; Here are some numbers I came up with running each of these cases a few times.&#160; The hit is a few percent, but it isn’t that huge.&#160; I saw roughly the same performance when scaling up to sets of around 100 million rows or so.</p>
<table border="1" cellspacing="0" cellpadding="2" width="315">
<tbody>
<tr>
<th valign="top" width="200">Test</td>
</th>
<th valign="top" width="113">Time</td>
</tr>
</th>
</tr>
<tr>
<td valign="top" width="248">Select (no casting, original fields, throwing away the data)</td>
<td valign="top" width="133">1:25</td>
</tr>
<tr>
<td valign="top" width="253">Select (coalesces, nullifs, casting)</td>
<td valign="top" width="144">1:26</td>
</tr>
<tr>
<td valign="top" width="248">Select (coalesces, nullifs, casting) + transformation</td>
<td valign="top" width="151">1:34</td>
</tr>
</tbody>
</table>
<h1>Some Things that Didn’t Work</h1>
<p>One thing that annoyed me about this solution was the COALESCE and NULLIF handling.&#160; Without this, though, a byte stream column will fail in SSIS.&#160; I did try changing the ErrorRowDisposition from RD_FailComponent to RD_IgnoreFailure.&#160; That allows nulls to come through.&#160; Unfortunately, at least in my sample, I found that doing this more than doubled the time it took to import the data.&#160; And even then, you have to use a Derived Column transform to create a flag column (on whether or not each field is null), then you have to handle nulls vs. non-nulls differently in the script transforms.&#160; It was a nice thought – and could work for some applications – but it wasn’t a good fit for my solution.</p>
<h1>Wrap-up</h1>
<p>The biggest issue I had doing all of this was figuring out what was stored in the source system, and how to transform it.&#160; There were actually a few extra flavors of data in the source system, but the approach above worked for all of them.</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2010/09/19/extracting-mysql-utf-8-fields-with-ssis/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Migration to Word Press</title>
		<link>http://agilebi.com/ddarden/2010/09/13/migration-to-word-press/</link>
		<comments>http://agilebi.com/ddarden/2010/09/13/migration-to-word-press/#comments</comments>
		<pubDate>Mon, 13 Sep 2010 13:52:24 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://agilebi.com/ddarden/?p=3</guid>
		<description><![CDATA[We recently migrated this site from Community Server to Word Press. Most of the posts came over pretty well, but some of the code samples and pictures didn&#8217;t make it. I&#8217;ll try and get these updated and fixed over the next week. Thanks for your patience&#8230;]]></description>
			<content:encoded><![CDATA[<p>We recently migrated this site from Community Server to Word Press.  Most of the posts came over pretty well, but some of the code samples and pictures didn&#8217;t make it.  I&#8217;ll try and get these updated and fixed over the next week.  Thanks for your patience&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2010/09/13/migration-to-word-press/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Presenting ‘Enterprise Class Analysis Services Development’ at the Vancouver PASS Chapter</title>
		<link>http://agilebi.com/ddarden/2010/03/10/presenting-enterprise-class-analysis-services-development-at-the-vancouver-pass-chapter/</link>
		<comments>http://agilebi.com/ddarden/2010/03/10/presenting-enterprise-class-analysis-services-development-at-the-vancouver-pass-chapter/#comments</comments>
		<pubDate>Wed, 10 Mar 2010 05:38:34 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">/cs/blogs/ddarden/archive/2010/03/10/presenting-enterprise-class-analysis-services-development-at-the-vancouver-pass-chapter.aspx</guid>
		<description><![CDATA[Hey Guys! I’m going to be giving a presentation on Enterprise Class Analysis Services Development at the Vancouver PASS Chapter Friday, March 12th.&#160; http://vancouverbi.sqlpass.org/Home/tabid/1551/Default.aspx I’ll be talking about some of the topics I’ve blogged about here, including working with multiple developers, using a custom MSBuild task to build SSAS Databases, and reviewing a monitoring/reporting solution [...]]]></description>
			<content:encoded><![CDATA[<p>Hey Guys!</p>
<p>I’m going to be giving a presentation on Enterprise Class Analysis Services Development at the Vancouver PASS Chapter Friday, March 12th.&#160; </p>
<p><a title="http://vancouverbi.sqlpass.org/Home/tabid/1551/Default.aspx" href="http://vancouverbi.sqlpass.org/Home/tabid/1551/Default.aspx">http://vancouverbi.sqlpass.org/Home/tabid/1551/Default.aspx</a></p>
<p>I’ll be talking about some of the topics I’ve blogged about here, including working with multiple developers, using a custom MSBuild task to build SSAS Databases, and reviewing a monitoring/reporting solution for SSAS.&#160; If you don’t happen to be in Vancouver, you can always attend virtually.&#160; <img src='http://agilebi.com/ddarden/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Title: Enterprise Class Analysis Services Development</p>
<p>SSAS is one of the most popular tools for OLAP, but many organizations experience challenges when attempting to use their standard development best practices with the tool. This session will cover many topics around enterprise development practices for SSAS, including how to effectively use source control with multiple developers, enable robust automated build/deployment strategies, implement usage monitoring and tracking solutions, and support unit testing for SSAS solutions.</p>
<p>Objective 1: Demonstrate how to use source control with multiple developers.</p>
<p>Objective 2: Show techniques to automate builds and enable robust deployment strategies.</p>
<p>Objective 3: Review strategies for robust monitoring of multiple SSAS deployments for development, administrative, and business purposes.</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2010/03/10/presenting-enterprise-class-analysis-services-development-at-the-vancouver-pass-chapter/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Using MSBuild with SQL Server Analysis Services Projects</title>
		<link>http://agilebi.com/ddarden/2009/11/16/using-msbuild-with-sql-server-analysis-services-projects/</link>
		<comments>http://agilebi.com/ddarden/2009/11/16/using-msbuild-with-sql-server-analysis-services-projects/#comments</comments>
		<pubDate>Mon, 16 Nov 2009 17:05:10 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[SSAS]]></category>

		<guid isPermaLink="false">/cs/blogs/ddarden/archive/2009/11/16/using-msbuild-with-sql-server-analysis-services-projects.aspx</guid>
		<description><![CDATA[I’ve written several blogs and community samples on working with SSAS Projects directly using AMO (instead of SSAS Databases on an Analysis Services server).  I was travelling this weekend, and got a chance to create a sample MSBuild task that will generate a .ASDatabase file directly from a Visual Studio project, without requiring Visual Studio [...]]]></description>
			<content:encoded><![CDATA[<p>I’ve written several blogs and community samples on working with SSAS Projects directly using AMO (instead of SSAS Databases on an Analysis Services server).  I was travelling this weekend, and got a chance to create a sample MSBuild task that will generate a .ASDatabase file directly from a Visual Studio project, without requiring Visual Studio itself.  This means that multiple developers can work on a project, check-in files via source control, and can schedule an automated build, build on a dedicated “clean” machine (without VS), or any of a number of other scenarios.</p>
<p>I added the custom MSBuild task to the <a href="http://sqlsrvanalysissrvcs.codeplex.com/" target="_blank">Analysis Services Community Samples</a> project on <a href="http://www.codeplex.com/" target="_blank">CodePlex</a> under the <a href="http://sqlsrvanalysissrvcs.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22769#DownloadId=84147" target="_blank">SsasHelper sample</a>.</p>
<p>The Build task code itself is trivial.  I won’t go into a lot of detail on that, has creating/debugging custom tasks is well documented (you can start the library with MSBuild.exe as the external program and the project file as the argument).  I used it as a wrapper for previously developed ProjectHelper code that does all the heavy lifting.  All we do is inherit from <em>Microsoft.Build.Utilities.Task </em>and implement the <em>Execute </em>method.  All I do in the task is de-serialize the project (based on the Visual Studio project passed in), validate the project (based on the target version of SSAS), and write out the .ASDatabase file.  This could of course be modified to use another method to deploy the database, but I’ve been using the .ASDatabase method for awhile with no issues.</p>
<p>Here’s the main code for the method:</p>
<pre><span class="kwrd">try</span>
{
Database database = ProjectHelper.DeserializeProject(SsasProjectFile);

<span class="rem">// ... Verify our project doesn't have any errors ...</span>
ValidationResultCollection results;

<span class="kwrd">bool</span> isValidated = ProjectHelper.ValidateDatabase(database, SsasServerEdition, <span class="kwrd">out</span> results);

<span class="rem">// If the database doesn't validate (i.e., a build error)</span>
<span class="rem">// log the errors and return failure.</span>
<span class="kwrd">foreach</span> (ValidationResult result <span class="kwrd">in</span> results)
{
Log.LogError(result.Description);
}

<span class="kwrd">if</span> (!isValidated)
{
<span class="kwrd">return</span> <span class="kwrd">false</span>;
}

<span class="rem">// Build the .ASDatabase file</span>
ProjectHelper.GenerateASDatabaseFile(database, SsasTargetFile);
}
<span class="kwrd">catch</span> (Exception ex)
{
Log.LogErrorFromException(ex);
<span class="kwrd">return</span> <span class="kwrd">false</span>;
}</pre>
<p><span class="kwrd">return</span> <span class="kwrd">true</span>;</p>
<p>So… how do we actually use this?  I included a sample project file in the <a href="http://sqlsrvanalysissrvcs.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22769#DownloadId=84147" target="_blank">SsasBuilder</a> project.  The basic idea is you have a project file (a file that ends in “.[optional prefix]proj”, such as “.proj”, “.csproj”, etc.).  You can call this via MSBuild.  Note that the standard SSAS project file DOES NOT work with MSBuild.  The schemas required for that project conflict with the MSBuild schema, so you’ll have to create another project file, or build the build step into somewhere else.  Here’s an example project file:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #0000ff">&lt;</span><span style="color: #800000">Project</span> <span style="color: #ff0000">xmlns</span><span style="color: #0000ff">="http://schemas.microsoft.com/developer/msbuild/2003"</span><span style="color: #0000ff">&gt;</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span>     <span style="color: #0000ff">&lt;</span><span style="color: #800000">UsingTask</span> <span style="color: #ff0000">TaskName</span><span style="color: #0000ff">="SsasBuilder.SsasBuildASDatabaseFileTask"</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>         <span style="color: #ff0000">AssemblyFile</span><span style="color: #0000ff">="C:\TFS\SsasHelper\SsasBuilder\bin\debug\SsasBuilder.dll"</span><span style="color: #0000ff">/&gt;</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span>     <span style="color: #0000ff">&lt;</span><span style="color: #800000">Target</span> <span style="color: #ff0000">Name</span><span style="color: #0000ff">="BuildASDatabaseFile"</span><span style="color: #0000ff">&gt;</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span>         <span style="color: #0000ff">&lt;</span><span style="color: #800000">SsasBuildASDatabaseFileTask</span> <span style="color: #ff0000">SsasProjectFile</span> = <span style="color: #0000ff">"C:\Test\enterprise_Gold\Adventure Works DW 2008.dwproj"</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>             <span style="color: #ff0000">SsasTargetFile</span> = <span style="color: #0000ff">"C:\Test\SsasBuildTest\AdventureWorks.ASDtabase"</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>             <span style="color: #ff0000">SsasServerEdition</span> = <span style="color: #0000ff">"Enterprise"</span> <span style="color: #0000ff">/&gt;</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>     <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Target</span><span style="color: #0000ff">&gt;</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span> <span style="color: #0000ff">&lt;/</span><span style="color: #800000">Project</span><span style="color: #0000ff">&gt;</span></pre>
<p><!--CRLF--></p>
</div>
</div>
<p>Here I’m using absolute paths, but you can use either properties or relative paths as required for your particular project.  You just use a <strong>UsingTask </strong>tag to point to the assembly containing the build task, then use the task in a target.  For this sample I’m I’m taking the SSAS project file and target filename, along with the server edition, as parameters.  If there are no errors in the project file, the .ASDatabase file will be generated in the specified location.</p>
<p>Now, all you have to do is call “MSBuild.exe &lt;Whateveryounamedyourproject&gt;”, and you’ll get a .ASDatabase file out of it…</p>
<p>Cheers,</p>
<p>David</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2009/11/16/using-msbuild-with-sql-server-analysis-services-projects/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Validating SSAS Projects Programmatically</title>
		<link>http://agilebi.com/ddarden/2009/11/06/validating-ssas-projects-programmatically/</link>
		<comments>http://agilebi.com/ddarden/2009/11/06/validating-ssas-projects-programmatically/#comments</comments>
		<pubDate>Fri, 06 Nov 2009 07:22:58 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[SSAS]]></category>

		<guid isPermaLink="false">/cs/blogs/ddarden/archive/2009/11/06/validating-ssas-projects-programmatically.aspx</guid>
		<description><![CDATA[Earlier this week I got a feature request from someone someone that was looking in to my SsasHelper sample on the Microsoft SQL Server Community Samples:&#160; Analysis Services site on CodePlex.&#160; She was interested in Building a SQL Server Analysis Services .ASDatabase file from a Visual Studio SSAS Project, but pointed out that I didn’t [...]]]></description>
			<content:encoded><![CDATA[
<p>Earlier this week I got a feature request from someone someone that was looking in to my <a href="http://sqlsrvanalysissrvcs.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22769#DownloadId=84147">SsasHelper</a> sample on the <a href="http://sqlsrvanalysissrvcs.codeplex.com/">Microsoft SQL Server Community Samples:&#160; Analysis Services</a> site on <a href="http://www.codeplex.com/">CodePlex</a>.&#160; She was interested in <a href="http://agilebi.com/cs/blogs/ddarden/archive/2009/05/31/building-a-sql-server-analysis-services-asdatabase-file-from-a-as-project.aspx" target="_blank">Building a SQL Server Analysis Services .ASDatabase file from a Visual Studio SSAS Project</a>, but pointed out that I didn’t actually <strong>*validate*</strong> a project before creating the .ASDatabase file, so if someone had checked in a project with errors, the whole process could blow up (or we might deploy a database with some issues).&#160; I looked into doing this, and it turns out it’s really easy to accomplish.&#160; I updated the code in <a href="http://sqlsrvanalysissrvcs.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22769#DownloadId=84147">SsasHelper</a> sample on <a href="http://www.codeplex.com/">CodePlex</a> to show how to do this.</p>
<p>The actual code is really simple:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #0000ff">bool</span> doesBuild = <span style="color: #0000ff">false</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> results = <span style="color: #0000ff">new</span> ValidationResultCollection();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span> <span style="color: #008000">// We have to provide a ServerEdition for this method to work.  There are </span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span> <span style="color: #008000">// overloads that look like the will work without them, but they can't be used</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span> <span style="color: #008000">// in this scenario.</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span> <span style="color: #008000">// The ServerEdition might need to be changed for your situation.</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span> <span style="color: #008000">// This can be modified to return warnings and messages as well.</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span> doesBuild = database.Validate(results, ValidationOptions.None, ServerEdition.Developer);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span> <span style="color: #0000ff">return</span> doesBuild;</pre>
<p><!--CRLF--></div>
</div>
<p>You can use the method I created like so:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> Database database;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> <span style="color: #0000ff">bool</span> hasErrors = <span style="color: #0000ff">false</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span> <span style="color: #008000">// Load a SSAS database object based on a BIDS project</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span> database = ProjectHelper.DeserializeProject(ssasProjectFile);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span> <span style="color: #008000">// ... Verify our project doesn't have any errors ...</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span> ValidationResultCollection results;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span> hasErrors = ProjectHelper.ValidateDatabase(database, <span style="color: #0000ff">out</span> results);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span> <span style="color: #0000ff">foreach</span> (ValidationResult result <span style="color: #0000ff">in</span> results)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>     Console.WriteLine(<span style="color: #0000ff">string</span>.Format(<span style="color: #006080">&quot;{0}&quot;</span>, result.Description));</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span> }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span> Console.WriteLine(<span style="color: #0000ff">string</span>.Format(<span style="color: #006080">&quot;Project is Error Free?  {0}&quot;</span>, hasErrors));</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span> Console.WriteLine(<span style="color: #006080">&quot;Project validated!&quot;</span>);</pre>
<p><!--CRLF--></div>
</div>
<p>This will take a Database object and validate it to see if there are any errors.&#160; You can modify it to return Warnings and Messages as well by changing the Validation Options.</p>
<p>One thing to note is that the Server Edition is a required parameter.&#160; There are a few overloads of the Validate method that don’t require this parameter, but what they try and do is walk up the object tree to get the Server object (associated with the Database) and retrieve the edition.&#160; Since I’m de-serializing a project into a Database object, this property isn’t available (and can’t be set).&#160; It is important to use this method with the correct Server Edition.&#160; The validation process will throw errors if you use some (but I don’t think <strong>*all*</strong>) of the features for a different edition.&#160; For example, if your project includes Translations (an Enterprise-only feature), validation will succeed if you use ServerEdition.Developer or ServerEdition.Enterprise, but will fail if you use ServerEdition.Standard.</p>
</p>
<p>Cheers,</p>
<p>David</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2009/11/06/validating-ssas-projects-programmatically/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SQL Server Analysis Services ‘Project Helper’</title>
		<link>http://agilebi.com/ddarden/2009/09/19/sql-server-analysis-services-project-helper/</link>
		<comments>http://agilebi.com/ddarden/2009/09/19/sql-server-analysis-services-project-helper/#comments</comments>
		<pubDate>Sat, 19 Sep 2009 06:52:56 +0000</pubDate>
		<dc:creator>ddarden</dc:creator>
				<category><![CDATA[AMO]]></category>
		<category><![CDATA[Analysis Services]]></category>
		<category><![CDATA[Automation Tools]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Library]]></category>

		<guid isPermaLink="false">/cs/blogs/ddarden/archive/2009/09/19/sql-server-analysis-services-project-helper.aspx</guid>
		<description><![CDATA[A little while back I spent an afternoon prototyping some functionality for Analysis Services.&#160; I’ve been working a bit on improving the collaborative development story around SSAS, and I was after Building a SQL Server Analysis Services .ASDatabase file from a Visual Studio SSAS Project. I did come up with some nifty functionality in the [...]]]></description>
			<content:encoded><![CDATA[
<p>A little while back I spent an afternoon prototyping some functionality for Analysis Services.&#160; I’ve been working a bit on improving the collaborative development story around SSAS, and I was after <a href="http://agilebi.com/cs/blogs/ddarden/archive/2009/05/31/building-a-sql-server-analysis-services-asdatabase-file-from-a-as-project.aspx" target="_blank">Building a SQL Server Analysis Services .ASDatabase file from a Visual Studio SSAS Project</a>. I did come up with some nifty functionality in the process that I thought I’d share.</p>
<h1>SsasHelper / ProjectHelper</h1>
<p>I added the <a href="http://sqlsrvanalysissrvcs.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22769#DownloadId=84147" target="_blank">SsasHelper</a> sample to the <a href="http://sqlsrvanalysissrvcs.codeplex.com/" target="_blank">Microsoft SQL Server Community Samples:&#160; Analysis Services</a> site on <a href="http://www.codeplex.com/" target="_blank">CodePlex</a>.&#160; </p>
<p>The <a href="http://sqlsrvanalysissrvcs.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=22769#DownloadId=84147" target="_blank">SsasHelper</a> sample currently only contains the ProjectHelper library, which contains some functions to help work with a Visual Studio SSAS project. I also built a command line interface to demonstrate how to use the library to do a few tasks. There are a bunch of things you can use the library for, but the sample should at least get you started. The sample has the following functionality:</p>
<ol>
<li>De-serialize a Visual Studio SSAS Project into an AMO Database, then re-serialize all the components (.cube, .dim, .dsv, etc.) back out to files. You could use this functionality to let you programmatically work with a AS DB. </li>
<li>Build a .ASDatabase file based on the Visual Studio SSAS Project. You could use this functionality as part of a robust build process. </li>
<li>&quot;Clean&quot; a Visual Studio SSAS Project for comparison/source code merges. This is basically the same functionality I wrote about <a href="http://agilebi.com/cs/blogs/ddarden/archive/2009/05/25/sql-server-analysis-services-projects-with-multiple-developers.aspx" target="_blank">here</a>, just ported into this library. </li>
<li>Re-order a SSAS Project File. This is basically the &quot;Smart Diff&quot; functionality from <a href="http://bidshelper.codeplex.com/" target="_blank">BIDS Helper</a>… I included it because De-serializing/serializing a project re-orders the .partition files, making them a pain to validate. I used this function to sort the .partition files to make them easy to diff. </li>
</ol>
<h2>Serialize/De-Serializing Visual Studio SSAS Projects</h2>
<p>This is something I’d be wanting to figure out for awhile.&#160; Most of this is really straight forward, with just one or two little tricks to make it work.&#160; We start by reading the SSAS Project file… we just load up the XML Document and parse each set of objects. It looks like this:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #008000">// Load the SSAS Project File</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> XmlReader reader = <span style="color: #0000ff">new</span> XmlTextReader(ssasProjectFile);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span> XmlDocument doc = <span style="color: #0000ff">new</span> XmlDocument();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span> doc.Load(reader);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span> <span style="color: #008000">// Load the Database</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span> nodeList = doc.SelectNodes(<span style="color: #006080">&quot;//Database/FullPath&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span> fullPath = nodeList[0].InnerText;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span> innerReader = <span style="color: #0000ff">new</span> XmlTextReader(ssasProjectDirectory + fullPath);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span> Utils.Deserialize(innerReader, (MajorObject)database);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span> <span style="color: #008000">// Load all the Datasources</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span> nodeList = doc.SelectNodes(<span style="color: #006080">&quot;//DataSources/ProjectItem/FullPath&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span> DataSource dataSource = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span> <span style="color: #0000ff">foreach</span> (XmlNode node <span style="color: #0000ff">in</span> nodeList)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>     fullPath = node.InnerText;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>     innerReader = <span style="color: #0000ff">new</span> XmlTextReader(ssasProjectDirectory + fullPath);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>     dataSource = <span style="color: #0000ff">new</span> RelationalDataSource();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>     Utils.Deserialize(innerReader, (MajorObject)dataSource);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>     database.DataSources.Add(dataSource);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>Loading each object type looks pretty much the same… the SSAS team provided that handy Utils class that helps to serialize and de-serialize objects.&#160; There is, of course, one little wrinkle… the .cube and .partition files.&#160; They don’t play by the same rules as the other objects.&#160; Luckily, they’re close enough.&#160; A few little hacks get us there.</p>
<p>The De-Serialize for cubes looks like this:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #008000">// Load all the Cubes</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> nodeList = doc.SelectNodes(<span style="color: #006080">&quot;//Cubes/ProjectItem/FullPath&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span> Cube cube = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span> <span style="color: #0000ff">foreach</span> (XmlNode node <span style="color: #0000ff">in</span> nodeList)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>     fullPath = node.InnerText;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>     innerReader = <span style="color: #0000ff">new</span> XmlTextReader(ssasProjectDirectory + fullPath);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>     cube = <span style="color: #0000ff">new</span> Cube();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>     Utils.Deserialize(innerReader, (MajorObject)cube);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>     database.Cubes.Add(cube);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span>     <span style="color: #008000">// Process cube dependencies (i.e. partitions</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>     <span style="color: #008000">// Little known fact:  The Serialize/Deserialize methods DO handle partitions... just not when </span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span>     <span style="color: #008000">// paired with anything else in the cube.  We have to do this part ourselves</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>     dependencyNodeList = node.ParentNode.SelectNodes(<span style="color: #006080">&quot;Dependencies/ProjectItem/FullPath&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span>     <span style="color: #0000ff">foreach</span> (XmlNode dependencyNode <span style="color: #0000ff">in</span> dependencyNodeList)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>         fullPath = dependencyNode.InnerText;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>         innerReader = ProjectHelper.FixPartitionsFileForDeserialize( ssasProjectDirectory + fullPath, cube);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>         Cube partitionsCube = <span style="color: #0000ff">new</span> Cube();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>         Utils.Deserialize(innerReader, (MajorObject)partitionsCube);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span>         MergePartitionCube(cube, partitionsCube);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  23:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  24:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>De-serializing the .cube files is easy… but I figured out you have to muck with the .partitions file a bit before it will de-serialize.&#160; We basically just have to add a Name node to the XML for the de-serialize to work… we don’t use it for anything, but it is required.</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> XmlReader FixPartitionsFileForDeserialize(<span style="color: #0000ff">string</span> partitionFilename,Cube sourceCube)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>     <span style="color: #008000">// Validate inputs</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span>     <span style="color: #0000ff">if</span> (sourceCube == <span style="color: #0000ff">null</span>)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>         <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentException(<span style="color: #006080">&quot;Provide a Cube object that matches the partitions file&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>     <span style="color: #0000ff">if</span> (<span style="color: #0000ff">string</span>.IsNullOrEmpty(partitionFilename))</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>         <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentException(<span style="color: #006080">&quot;Provide a partitions file&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>     <span style="color: #008000">// I am NOT validating the extention to provide some extra flexibility here</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>     XmlDocument document = <span style="color: #0000ff">new</span> XmlDocument();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span>     document.Load(partitionFilename);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>     <span style="color: #008000">// Setup for XPath queries</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>     XmlNamespaceManager xmlnsManager = LoadSsasNamespaces(document);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>     <span style="color: #0000ff">string</span> defaultNamespaceURI = <span style="color: #006080">&quot;http://schemas.microsoft.com/analysisservices/2003/engine&quot;</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>      </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span>     <span style="color: #008000">// Get all the MeasureGroup IDs</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  23:</span>     XmlNodeList nodeList = document.SelectNodes(<span style="color: #006080">&quot;/AS:Cube/AS:MeasureGroups/AS:MeasureGroup/AS:ID&quot;</span>, xmlnsManager);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  24:</span>     XmlNode newNode = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  25:</span>     </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  26:</span>     <span style="color: #008000">// Add a Name node underneath the ID node if one doesn't exist, using the MeasureGroup's real name</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  27:</span>     <span style="color: #0000ff">foreach</span> (XmlNode node <span style="color: #0000ff">in</span> nodeList)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  28:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  29:</span>         <span style="color: #008000">// Verify the node doesn't exist</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  30:</span>         <span style="color: #0000ff">if</span> (XmlHelper.NodeExists(node.ParentNode, <span style="color: #006080">&quot;Name&quot;</span>))</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  31:</span>             <span style="color: #0000ff">continue</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  32:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  33:</span>         newNode = document.CreateNode(XmlNodeType.Element, <span style="color: #006080">&quot;Name&quot;</span>, defaultNamespaceURI);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  34:</span>         <span style="color: #008000">// Lookup the MG name from the cube based on the ID in the file</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  35:</span>         newNode.InnerText = sourceCube.MeasureGroups.Find(node.InnerText).Name;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  36:</span>         node.ParentNode.InsertAfter (newNode, node);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  37:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  38:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  39:</span>     <span style="color: #008000">// Return this as an XmlReader, so it can be manipulated</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  40:</span>     <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> XmlTextReader(<span style="color: #0000ff">new</span> StringReader(document.OuterXml));</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  41:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>Next, the MergeParition method will just loop thru the “partitions only” cube we de-serialized, and add each partition and aggregation design to the “main” cube.&#160; We have to make sure a create a copy of each object we add, because a Partition and AggregationDesign can only be in a single cube… using a Add on an existing object removes it from the initial collection.</p>
<div class="csharpcode-wrapper">
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> MergePartitionCube(Cube baseCube, Cube partitionCube)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>     MeasureGroup baseMG = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span>     <span style="color: #0000ff">foreach</span> (MeasureGroup mg <span style="color: #0000ff">in</span> partitionCube.MeasureGroups)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>         baseMG = baseCube.MeasureGroups.Find(mg.ID);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>         <span style="color: #008000">// Heisenberg principle in action with these objects; use 'for' instead of 'foreach'</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>         <span style="color: #0000ff">if</span> (mg.Partitions.Count &gt; 0)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>         {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span>             <span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> i = 0; i &lt; mg.Partitions.Count; ++i)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>             {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span>                 Partition partitionCopy = mg.Partitions[i].Clone();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>                 baseMG.Partitions.Add(partitionCopy);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span>             }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>         }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>         <span style="color: #008000">// Heisenberg principle in action with these objects; use 'for' instead of 'foreach'</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>         <span style="color: #0000ff">if</span> (mg.AggregationDesigns.Count &gt; 0)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>         {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span>             <span style="color: #0000ff">for</span> (<span style="color: #0000ff">int</span> i = 0; i &lt; mg.AggregationDesigns.Count; ++i)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  23:</span>             {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  24:</span>                 AggregationDesign aggDesignCopy = mg.AggregationDesigns[i].Clone();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  25:</span>                 baseMG.AggregationDesigns.Add(aggDesignCopy);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  26:</span>             }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  27:</span>         }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  28:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  29:</span> }</pre>
<p><!--CRLF--></div>
</p></div>
</div>
<p>We now have a AMO Database from our Visual Studio Project!&#160; Serializing it back out is pretty similar… we just loop through the Database, and serialize everything out:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> XmlTextWriter writer = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span> <span style="color: #008000">// Iterate through all objects in the database and serialize them</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span> <span style="color: #0000ff">foreach</span> (DataSource dataSource <span style="color: #0000ff">in</span> database.DataSources)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>     writer = <span style="color: #0000ff">new</span> XmlTextWriter(targetDirectory + dataSource.Name + <span style="color: #006080">&quot;.ds&quot;</span>, Encoding.UTF8);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>     writer.Formatting = Formatting.Indented;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>     Utils.Serialize(writer, (MajorObject)dataSource, <span style="color: #0000ff">false</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>     writer.Close();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>Again, the one wrinkle is the .cube and .partitions file… the Utils methods don’t 100% work with those guys.&#160; Luckily, they work about 90%, so we have a work around.</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #008000">// Special case:  The cube serialization won't work for partitions when Partion/AggregationDesign</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> <span style="color: #008000">// objects are mixed in with other objects.  Serialize most of the cube, then split out</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span> <span style="color: #008000">// Partion/AggregationDesign objects into their own cube to serialize, then clean up</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span> <span style="color: #008000">// a few tags</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span> <span style="color: #0000ff">foreach</span> (Cube cube <span style="color: #0000ff">in</span> database.Cubes)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>     writer = <span style="color: #0000ff">new</span> XmlTextWriter(targetDirectory + cube.Name + <span style="color: #006080">&quot;.cube&quot;</span>, Encoding.UTF8);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>     writer.Formatting = Formatting.Indented;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>     Utils.Serialize(writer, (MajorObject)cube, <span style="color: #0000ff">false</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>     writer.Close();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span>     <span style="color: #008000">// Partitions and AggregationDesigns may be written to the Cube file, and we want</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>     <span style="color: #008000">// to keep them all in the Partitions file; strip them from the cube file</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span>     FixSerializedCubeFile(targetDirectory + cube.Name + <span style="color: #006080">&quot;.cube&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span>     Cube partitionCube = SplitPartitionCube(cube);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>     writer = <span style="color: #0000ff">new</span> XmlTextWriter(targetDirectory + cube.Name + <span style="color: #006080">&quot;.partitions&quot;</span>, Encoding.UTF8);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>     writer.Formatting = Formatting.Indented;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>     Utils.Serialize(writer, (MajorObject)partitionCube, <span style="color: #0000ff">false</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>     writer.Close();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span>     <span style="color: #008000">// The partitions file gets serialized with a few extra nodes... remove them</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  23:</span>     FixSerializedPartitionsFile(targetDirectory + cube.Name + <span style="color: #006080">&quot;.partitions&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  24:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>We first serialize the whole cube (for each cube, of course) and then manually fix up the file.&#160; Some of the Partition and Measuregroup info gets serialized out to the .cube file at this point, and we want to keep all of that information in the .partition file where it belongs.</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> FixSerializedCubeFile(<span style="color: #0000ff">string</span> cubeFilename)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>     <span style="color: #008000">// Validate inputs</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span>     <span style="color: #0000ff">if</span> (<span style="color: #0000ff">string</span>.IsNullOrEmpty(cubeFilename))</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>         <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentException(<span style="color: #006080">&quot;Provide a cube file&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>     <span style="color: #008000">// I am NOT validating the extention to provide some extra flexibility here</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>     XmlDocument document = <span style="color: #0000ff">new</span> XmlDocument();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>     document.Load(cubeFilename);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>     XmlNamespaceManager xmlnsManager = LoadSsasNamespaces(document);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>     XmlNodeList nodeList = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>     <span style="color: #008000">// Remove the MeasureGroup Names</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>     nodeList = document.SelectNodes(<span style="color: #006080">&quot;/AS:Cube/AS:MeasureGroups/AS:MeasureGroup/AS:Partitions&quot;</span>, xmlnsManager);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>     XmlHelper.RemoveNodes(nodeList);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>     <span style="color: #008000">// Remove the StorageModes</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span>     nodeList = document.SelectNodes(<span style="color: #006080">&quot;/AS:Cube/AS:MeasureGroups/AS:MeasureGroup/AS:AggregationDesigns&quot;</span>, xmlnsManager);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  23:</span>     XmlHelper.RemoveNodes(nodeList);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  24:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  25:</span>     document.Save(cubeFilename);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  26:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>We then create a temporary cube with just the Partition and AggregationDesign objects in it, and serialize it.&#160; Again, we have to clean up the file just a bit.</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> FixSerializedPartitionsFile(<span style="color: #0000ff">string</span> partitionFilename)</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span> {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span>     <span style="color: #008000">// Validate inputs</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span>     <span style="color: #0000ff">if</span> (<span style="color: #0000ff">string</span>.IsNullOrEmpty(partitionFilename))</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span>     {</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   6:</span>         <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentException(<span style="color: #006080">&quot;Provide a partitions file&quot;</span>);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   7:</span>     }</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   8:</span>     <span style="color: #008000">// I am NOT validating the extention to provide some extra flexibility here</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   9:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  10:</span>     XmlDocument document = <span style="color: #0000ff">new</span> XmlDocument();</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  11:</span>     document.Load(partitionFilename);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  12:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  13:</span>     XmlNamespaceManager xmlnsManager = LoadSsasNamespaces(document);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  14:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  15:</span>     XmlNodeList nodeList = <span style="color: #0000ff">null</span>;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  16:</span>     </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  17:</span>     <span style="color: #008000">// Remove the MeasureGroup Names</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  18:</span>     nodeList = document.SelectNodes(<span style="color: #006080">&quot;/AS:Cube/AS:MeasureGroups/AS:MeasureGroup/AS:Name&quot;</span>, xmlnsManager);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  19:</span>     XmlHelper.RemoveNodes(nodeList);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  20:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  21:</span>     <span style="color: #008000">// Remove the StorageModes</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  22:</span>     nodeList = document.SelectNodes(<span style="color: #006080">&quot;/AS:Cube/AS:MeasureGroups/AS:MeasureGroup/AS:StorageMode&quot;</span>, xmlnsManager);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  23:</span>     XmlHelper.RemoveNodes(nodeList);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  24:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  25:</span>     <span style="color: #008000">// Remove the ProcessingModes</span></pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  26:</span>     nodeList = document.SelectNodes(<span style="color: #006080">&quot;/AS:Cube/AS:MeasureGroups/AS:MeasureGroup/AS:ProcessingMode&quot;</span>, xmlnsManager);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  27:</span>     XmlHelper.RemoveNodes(nodeList);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  28:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  29:</span>     document.Save(partitionFilename);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">  30:</span> }</pre>
<p><!--CRLF--></div>
</div>
<p>…and there we go!&#160; We can start with a Visual Studio SSAS Project, create the Database in memory, do whatever we want with it, then write it back out to files!</p>
<h2>Build .ASDatabase File</h2>
<p>I talk more about the what and why in &lt;&lt;this blog&gt;&gt;, but the actual mechanics of this functionality is really easy (once we’ve de-serialized the project, that is).&#160; This could basically be condensed to a single line of code:</p>
<div>
<div>
<pre><span style="color: #606060">   1:</span> Database database;</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   2:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   3:</span> database = ProjectHelper.DeserializeProject(ssasProjectFile);</pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   4:</span>&#160; </pre>
<p><!--CRLF--></p>
<pre><span style="color: #606060">   5:</span> ProjectHelper.GenerateASDatabaseFile(database, targetFilename);</pre>
<p><!--CRLF--></div>
</div>
<h1>&#160;</h1>
<h2>Clean Visual Studio SSAS Project</h2>
<p>I won’t go into a lot of detail on this here… I posted a blog on <a href="http://agilebi.com/cs/blogs/ddarden/archive/2009/05/25/sql-server-analysis-services-projects-with-multiple-developers.aspx" target="_blank">SQL Server Analysis Services Projects with Multiple Developers</a> a little while back.&#160; I originally wrote this functionality in PowerShell, but just ported it into this library because I thought it would be useful.&#160; This method just goes through each unlocked file and removes fields maintained by Visual Studio that aren’t required by the AS DB per se, but that make merging code from multiple developers substantially more difficult.&#160; If you’re testing out the serialize/de-serialize functionality, you might also want to use this against your projects to make them easier to compare.</p>
<h2>Sort SSAS Project File</h2>
<p>One annoying side affect of serializing the .partition file is that all the elements get re-arranged.&#160; Everything still works, of course, but if you’re like me you’ll want to verify that everything is working.&#160; The good people over at <a href="http://bidshelper.codeplex.com/" target="_blank">BIDS Helper</a> let me use a version of their “Smart Diff” XSLT that, which will sort a SSAS file.&#160; I modified it slightly and customized it to help validate the .partition files.&#160; I would be very careful using it for anything else… its only been tested for the intended scenario.</p>
<h1>Known Issues</h1>
<p>I’ve tested this with Visual Studio 2008 SP1, SQL Server Analysis Services SP1 CU3, and the <a href="http://msftdbprodsamples.codeplex.com/" target="_blank">Adventure Works 2008 AS DB</a> with no issues.&#160; Definitely make a backup before you try this out, as your mileage may vary.</p>
<ol>
<li>The sample I wrote just serializes objects into files based on their names.&#160; It would be trivial to maintain a list based on the Visual Studio project file to make sure the objects are written out with their correct names. </li>
<li>Partitions are reordered when De-Serialized/Serialized. Makes it a pain to validate, but I&#8217;ve seen no ill effects. Use SortSssasFile functionality to make a copy of the files for easier comparison. </li>
<li>Some fields maintained for Visual Studio (State (Processed, Unprocessed), CreatedTimestamp, LastSchemaUpdate, LastProcessed, dwd:design-time-name, CurrentStorageMode) are lost when a project is De-Serialized/Serialized. </li>
</ol>
<h1>Next Steps</h1>
<p>I’ve thought of some nifty things to do with this functionality, including…</p>
<ol>
<li>Generate a new Visual Studio project based on metadata. </li>
<li>Maintain a project with external metadata… you can maintain a separate data dictionary with descriptions of each attribute/measure/cube, and just programmatically update your source when needed. </li>
<li>Automate repetitive tasks and maintenance of the project (like adding a bunch of partitions to your measure groups, or replacing every occurrence of a certain word in the project. </li>
<li>Build a more robust development and deployment process. </li>
<li>…and many more things. </li>
</ol>
<p>Cheers,</p>
<p>David</p>
]]></content:encoded>
			<wfw:commentRss>http://agilebi.com/ddarden/2009/09/19/sql-server-analysis-services-project-helper/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
