<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.adatis.co.uk/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Jeremy Kashel&amp;#39;s Blog : C#</title><link>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/C_2300_/default.aspx</link><description>Tags: C#</description><dc:language>en</dc:language><generator>CommunityServer 2007 SP2 (Build: 20611.960)</generator><item><title>Debug Custom MDS Workflows</title><link>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/2013/05/20/debug-custom-mds-workflows.aspx</link><pubDate>Mon, 20 May 2013 14:19:04 GMT</pubDate><guid isPermaLink="false">8d7d37f8-4a66-4c95-9fba-293fa87607dc:13437</guid><dc:creator>Jeremy Kashel</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.adatis.co.uk/blogs/jeremykashel/rsscomments.aspx?PostID=13437</wfw:commentRss><comments>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/2013/05/20/debug-custom-mds-workflows.aspx#comments</comments><description>&lt;p&gt;The custom MDS workflows, AKA External Action Business Rules or custom MDS assemblies, are one of the more advanced features of MDS. They allow you to extend the standard MDS business rules by getting MDS to fire Entity Member member data at an external assembly of your choice.&lt;/p&gt;  &lt;p&gt;There are a lot of moving parts to this feature, MDS business rules, the custom .Net assembly, service broker and a windows service! When a business rule meets your condition, then the rule is written to a service broker queue. The windows service interrogates the queue and then passes the information to the custom assembly. I’m not going to show how to do this, this is already done by &lt;a href="http://johanmachielse.blogspot.co.uk/2011/07/master-data-services-implementing.html" target="_blank"&gt;Johan Machielse&lt;/a&gt; and also on &lt;a href="http://msdn.microsoft.com/en-gb/library/hh270298.aspx" target="_blank"&gt;MSDN&lt;/a&gt;. What I would like to show is how to debug the use of the custom business rules, just due to the number of moving parts and also a couple of quirks that I’ve encountered when using them.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Custom Assembly Deployment&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;One of the first things that may catch you out is the deployment of the assembly. To troubleshoot this, I recommend the&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; “-console” argument of the workflow service. If you run it in command line mode you may encounter a few errors:&lt;/p&gt;  &lt;p&gt;&lt;em&gt;“Could not load file or assembly &amp;#39;YourAssembly&amp;#39; or one of its dependencies. This assembly is built by a runtime newer than      &lt;br /&gt;the currently loaded runtime and cannot be loaded.”&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;This means that you need to go the properties of your assembly in Visual Studio and change the Target Framework from .NET Framework 4 to 3.5.&lt;/p&gt;  &lt;p&gt;Another error that you may get is &amp;quot;&lt;em&gt;ERROR: Could not create type. Ignored.”&lt;strong&gt;&amp;#160; &lt;/strong&gt;&lt;/em&gt;Assuming that you have actually put the assembly in the bin directory, this is most likely to be that the either the namespace or the class name in your DLL doesn’t match what you have in your config file. Check the contents of Microsoft.MasterDataServices.Workflow.exe.config match your DLL.&lt;/p&gt;  &lt;p&gt;You may also get, “&lt;em&gt;ERROR: Type does not implement IWorkflowTypeExtender.&lt;/em&gt;” This means that your assembly has been picked up correctly, but it does not implement IWorkflowTypeExtender.&lt;/p&gt;  &lt;p&gt;If the assembly does start up ok without errors, you may still get errors when the exe tries to take a message off the service broker queue. An error such as &lt;em&gt;“Could not dispatch due to missing extender &amp;lt;tag&amp;gt;”&lt;/em&gt; means that the tag that you’ve given to the assembly in the config file does not match the “Workflow Type” value of the MDS business rule:&lt;/p&gt;  &lt;p&gt;   &lt;pre class="code"&gt;&lt;strong&gt;&lt;span style="color:blue;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#a31515;"&gt;setting &lt;/span&gt;&lt;span style="color:red;"&gt;name&lt;/span&gt;&lt;span style="color:blue;"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color:blue;"&gt;WorkflowTypeExtenders&lt;/span&gt;&amp;quot; &lt;span style="color:red;"&gt;serializeAs&lt;/span&gt;&lt;span style="color:blue;"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color:blue;"&gt;String&lt;/span&gt;&amp;quot;&lt;/strong&gt;&lt;strong&gt;&lt;span style="color:blue;"&gt;&amp;gt;
&amp;lt;&lt;/span&gt;&lt;span style="color:#a31515;"&gt;value&lt;/span&gt;&lt;span style="color:blue;"&gt;&amp;gt;&lt;/span&gt;&lt;/strong&gt;&lt;font style="background-color:#ffff00;"&gt;MDSTEST&lt;/font&gt;=MDS.CustomWorkflow, CustomWorkflow&lt;strong&gt;&lt;span&gt;&amp;lt;/value&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color:#a31515;"&gt;setting&lt;/span&gt;&lt;span style="color:blue;"&gt;&amp;gt;&lt;/span&gt;&lt;/strong&gt;&lt;/pre&gt;

  &lt;pre class="code"&gt;&lt;a href="http://blogs.adatis.co.uk/blogs/jeremykashel/image_189D954D.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://blogs.adatis.co.uk/blogs/jeremykashel/image_thumb_0D740B03.png" width="244" height="97" /&gt;&lt;/a&gt;&lt;/pre&gt;
  &lt;strong&gt;Debugging the Custom Assembly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you do get the assembly working, you may find that the code isn’t quite doing what you want. A good way to troubleshoot this is to choose “Attach To Process” from the Debug menu in Visual Studio, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.adatis.co.uk/blogs/jeremykashel/image3_266FDB48.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://blogs.adatis.co.uk/blogs/jeremykashel/image3_thumb_545D2E00.png" width="409" height="278" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do this, then you can add breakpoints to your .Net code as normal and step through in order to troubleshoot issues.&lt;/p&gt;

&lt;p&gt;To conclude, this probably isn’t an exhaustive list of all the sort of errors that could occur with custom workflows, but with any luck it will save someone a few hours of troubleshooting.&lt;/p&gt;&lt;img src="http://blogs.adatis.co.uk/aggbug.aspx?PostID=13437" width="1" height="1"&gt;</description><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/MDM/default.aspx">MDM</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/Master+Data+Services/default.aspx">Master Data Services</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/MDS/default.aspx">MDS</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/Master+Data+Management/default.aspx">Master Data Management</category></item><item><title>Managing SSAS Named Sets with Master Data Services Collections</title><link>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/2011/11/07/managing-ssas-named-sets-with-master-data-services-collections.aspx</link><pubDate>Mon, 07 Nov 2011 17:04:00 GMT</pubDate><guid isPermaLink="false">8d7d37f8-4a66-4c95-9fba-293fa87607dc:10006</guid><dc:creator>Jeremy Kashel</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.adatis.co.uk/blogs/jeremykashel/rsscomments.aspx?PostID=10006</wfw:commentRss><comments>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/2011/11/07/managing-ssas-named-sets-with-master-data-services-collections.aspx#comments</comments><description>&lt;p&gt;Master Data Services &lt;a href="http://msdn.microsoft.com/en-us/library/ee633733.aspx" target="_blank"&gt;Collections&lt;/a&gt; are probably not the first feature that come to mind when you think of the MDS product. The hierarchies and member grids tend to be the core functionality, but as this post will hopefully show, MDS Collections are useful also.&lt;/p&gt;  &lt;p&gt;Collections are essentially managed lists of members that can come from multiple different MDS explicit hierarchies, or also from another collection. The idea is that this “subset of members” can be maintained in MDS by a business user and then fed to external applications for reporting or other purposes.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Analysis Services Named Sets&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;One example of how collections can be used is to maintain Analysis Services named sets. Some named sets, such as a Top 50 Customers, don&amp;#39;t require any maintenance, as it’s just the top 50 of all customers, based on a particular measure. On the other hand, sometimes named sets can be static lists, e.g. a list of core products that the user wants to see for reporting.&lt;/p&gt;  &lt;p&gt;In the latter example, if a user wants the definition of a named set to change, they have to get IT to change the MDX script. MDS collections can help by allowing the user to control the named set definition, reducing the burden on IT.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Example Scenario&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;  &lt;p&gt;Here’s an example of how this is done. First of all, the end game is that I have a named set in Analysis Services that is currently just for 3 products. Therefore, a user can easily drop this set into an Excel report to get quick access to the products that are important to them:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.adatis.co.uk/blogs/jeremykashel/image_3AD09CE1.png"&gt;&lt;img style="border:0px currentColor;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://blogs.adatis.co.uk/blogs/jeremykashel/image_thumb_6132802C.png" width="343" height="114" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;So the challenge is that we need to find some way of extending this named set without doing it manually. This is where MDS starts to come in, so using the sample Product model that comes with MDS, I’ve created a new collection called Favourite Products, as shown in the image below:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.adatis.co.uk/blogs/jeremykashel/image_0008F40B.png"&gt;&lt;img style="border:0px currentColor;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://blogs.adatis.co.uk/blogs/jeremykashel/image_thumb_033ADBF3.png" width="454" height="173" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;If I go and edit the Favourite Products collection in MDS, then I can drag and drop more products into this collection, or remove some existing members. In this case, I’ve chosen to add two new products:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://blogs.adatis.co.uk/blogs/jeremykashel/image_01F64314.png"&gt;&lt;img style="border:0px currentColor;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://blogs.adatis.co.uk/blogs/jeremykashel/image_thumb_1D2E9C15.png" width="380" height="172" /&gt;&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;&lt;strong&gt;C#, AMO and the MDS API&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;So the named set represents the target that we need to get to, whereas the MDS collection that’s shown is the source. To get the contents of the MDS collection to update the named set, one way of doing it is to use the MDS API to pick up the collection members, and then to use AMO in order to write the named set into the MDX script. I’m just doing this in a C# windows application, but you could do it via a batch process, such as SSIS. For this post I’m just going to show the code, so here goes:&lt;/p&gt;  &lt;p&gt;This post is already starting to feel too long so I’m not going to show the basics of the MDS API. For that, take a look at a good posting by the MDS Team blog &lt;a href="http://sqlblog.com/blogs/mds_team/archive/2010/01/12/getting-started-with-the-web-services-api-in-sql-server-2008-r2-master-data-services.aspx" target="_blank"&gt;here&lt;/a&gt;. Also, as anyone who knows me will no doubt agree, I don’t tend to get involved in doing C#, so don’t consider this to be production ready! It should give you an idea of the basics though. Anyway, assuming that we’re now connected to the MDS Web Service, I’ve created the following method that will return the members from the collection:&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color:blue;"&gt;private &lt;/span&gt;&lt;span&gt;HierarchyMembers &lt;/span&gt;ObtainHierarchyMembers(&lt;span style="color:blue;"&gt;string &lt;/span&gt;entityId, &lt;span style="color:blue;"&gt;string &lt;/span&gt;hierarchyId, &lt;span style="color:blue;"&gt;string &lt;/span&gt;modelId, &lt;span style="color:blue;"&gt;string &lt;/span&gt;versionId)
{
    &lt;span&gt;HierarchyMembersGetRequest &lt;/span&gt;request = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;HierarchyMembersGetRequest&lt;/span&gt;();
    &lt;span&gt;HierarchyMembersGetResponse &lt;/span&gt;response = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;HierarchyMembersGetResponse&lt;/span&gt;();
    request.HierarchyMembersGetCriteria = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;HierarchyMembersGetCriteria&lt;/span&gt;();

    &lt;span style="color:green;"&gt;//State that our hierarhcy type is a collection and that we want collection members
    &lt;/span&gt;request.HierarchyMembersGetCriteria.HierarchyType = &lt;span&gt;HierarchyType&lt;/span&gt;.Collection;
    &lt;span style="color:green;"&gt;//Pass in the key search criteria to identify the correct collection in MDS
    &lt;/span&gt;request.HierarchyMembersGetCriteria.ParentEntityId = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;Identifier &lt;/span&gt;{ Name = entityId };
    request.HierarchyMembersGetCriteria.HierarchyId = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;Identifier &lt;/span&gt;{ Name = hierarchyId };
    request.HierarchyMembersGetCriteria.ModelId = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;Identifier &lt;/span&gt;{ Name = modelId };
    request.HierarchyMembersGetCriteria.VersionId = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;Identifier &lt;/span&gt;{ Name = versionId };

    request.HierarchyMembersGetCriteria.RowLimit = 50;
    request.International = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;International&lt;/span&gt;();
    &lt;span&gt;OperationResult &lt;/span&gt;result = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;OperationResult&lt;/span&gt;();
    &lt;span style="color:green;"&gt;//Return the hierarchy members from the service
    &lt;/span&gt;&lt;span style="color:blue;"&gt;return &lt;/span&gt;service.HierarchyMembersGet(request.International, request.HierarchyMembersGetCriteria, &lt;span style="color:blue;"&gt;out &lt;/span&gt;result);
}&lt;/pre&gt;

&lt;p&gt;Before we use the above method, we need to connect to SSAS using AMO. That can be done quite easily with the following code:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span&gt;Server &lt;/span&gt;server = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;Server&lt;/span&gt;();
&lt;span style="color:blue;"&gt;string &lt;/span&gt;connection = &lt;span&gt;&amp;quot;Data Source=.;Catalog=Adventure Works DW 2008R2;&amp;quot;&lt;/span&gt;;
server.Connect(connection);&lt;/pre&gt;

&lt;p&gt;After we’ve done all the usual error handling associated with connecting to a database, we need to pick up the SSAS database and cube:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span&gt;Database &lt;/span&gt;database = server.Databases[&lt;span&gt;&amp;quot;Adventure Works DW 2008R2&amp;quot;&lt;/span&gt;];
&lt;span&gt;Cube &lt;/span&gt;cube = database.Cubes[&lt;span&gt;&amp;quot;Adventure Works&amp;quot;&lt;/span&gt;];&lt;/pre&gt;

&lt;p&gt;Now we’re ready to call our ObtainHierarchyMembers method, before looping through it to build the named set:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span&gt;StringBuilder &lt;/span&gt;mdx = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;StringBuilder&lt;/span&gt;(&lt;span&gt;&amp;quot;\n//Auto generated named set at &amp;quot; &lt;/span&gt;+ &lt;span&gt;DateTime&lt;/span&gt;.Now.ToString() + 
    &lt;span&gt;&amp;quot;\nCREATE SET CurrentCube.[Favourite Products] AS {&amp;quot;&lt;/span&gt;);
&lt;span style="color:blue;"&gt;int &lt;/span&gt;count = 1;
&lt;span style="color:green;"&gt;//Loop through the collection to build the mdx
&lt;/span&gt;&lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span&gt;ParentChild &lt;/span&gt;pc &lt;span style="color:blue;"&gt;in &lt;/span&gt;hm.Members)
{
    &lt;span style="color:green;"&gt;//Add the members to the MDX string
    //This references the member by name
    //It would be possible to reference by member key, but would require more work
    &lt;/span&gt;mdx.Append(&lt;span&gt;&amp;quot;[Product].[Product].[&amp;quot; &lt;/span&gt;+ pc.Child.Name + &lt;span&gt;&amp;quot;]&amp;quot;&lt;/span&gt;);
    &lt;span style="color:blue;"&gt;if &lt;/span&gt;(count &amp;lt; hm.Members.Count())
    {
        mdx.Append(&lt;span&gt;&amp;quot;, &amp;quot;&lt;/span&gt;);
    }
    count++;
}
mdx.Append(&lt;span&gt;&amp;quot;};&amp;quot;&lt;/span&gt;);&lt;/pre&gt;


&lt;p&gt;Now we need to insert the named set in the correct place within the existing MDX script, bearing in mind it could already exist:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color:blue;"&gt;string &lt;/span&gt;currentScript = cube.MdxScripts[0].Commands[0].Text;
&lt;span style="color:green;"&gt;//Find the correct place to insert the named set within the MDX script:
&lt;/span&gt;&lt;span style="color:blue;"&gt;int &lt;/span&gt;start = currentScript.IndexOf(&lt;span&gt;&amp;quot;\n//Auto generated named set at&amp;quot;&lt;/span&gt;);
&lt;span style="color:blue;"&gt;int &lt;/span&gt;end = 0;
&lt;span&gt;StringBuilder &lt;/span&gt;newScript = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span&gt;StringBuilder&lt;/span&gt;(currentScript);
&lt;span style="color:blue;"&gt;if &lt;/span&gt;(start != -1)
{
    end = currentScript.IndexOf(&lt;span&gt;&amp;quot;;&amp;quot;&lt;/span&gt;, start);
    &lt;span style="color:green;"&gt;//If the named set already exists, remove it
    &lt;/span&gt;newScript.Remove(start, end - start + 1);
}
&lt;span style="color:blue;"&gt;else
&lt;/span&gt;{
    start = currentScript.Length;
}
&lt;span style="color:green;"&gt;//Insert the named set in the correct place
&lt;/span&gt;newScript.Insert(start, mdx.ToString());
&lt;span style="color:green;"&gt;//Update the cube&amp;#39;s MDX script
&lt;/span&gt;cube.MdxScripts[0].Commands[0].Text = newScript.ToString();&lt;/pre&gt;

&lt;p&gt;Finally we just need to update the cube in order to write the MDX back to the cube:&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color:green;"&gt;//Call the update methods to update the cube
&lt;/span&gt;cube.MdxScripts[0].Update();
cube.Update();&lt;/pre&gt;




&lt;p&gt;&lt;strong&gt;User Reports&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that the cube has been updated, if the Excel report is refreshed, then the two new products will appear in the list:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://blogs.adatis.co.uk/blogs/jeremykashel/image_06F880C3.png"&gt;&lt;img style="border:0px currentColor;padding-top:0px;padding-right:0px;padding-left:0px;display:inline;background-image:none;" title="image" border="0" alt="image" src="http://blogs.adatis.co.uk/blogs/jeremykashel/image_thumb_145E93C9.png" width="348" height="150" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Controlling SSAS named sets like this won’t be for everybody. I’ve certainly worked in a few clients where strict process has to be followed to update this sort of thing, but I can think of other companies that I know where this would be really useful.&lt;/p&gt;

&lt;p&gt;Managing Analysis Services named sets is just one use for collections. Another example might be managing default multi-select parameters for SSRS reports. As collections are just lists of members that can be extracted easily, what you do with them is up to you!&lt;/p&gt;&lt;img src="http://blogs.adatis.co.uk/aggbug.aspx?PostID=10006" width="1" height="1"&gt;</description><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/MDX/default.aspx">MDX</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/Analysis+Services/default.aspx">Analysis Services</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/MDM/default.aspx">MDM</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/Master+Data+Services/default.aspx">Master Data Services</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/MDS/default.aspx">MDS</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/Master+Data+Management/default.aspx">Master Data Management</category></item><item><title>Audit Trail in PerformancePoint Planning</title><link>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/2008/12/15/audit-trail-in-performancepoint-planning.aspx</link><pubDate>Mon, 15 Dec 2008 15:49:05 GMT</pubDate><guid isPermaLink="false">8d7d37f8-4a66-4c95-9fba-293fa87607dc:7485</guid><dc:creator>Jeremy Kashel</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.adatis.co.uk/blogs/jeremykashel/rsscomments.aspx?PostID=7485</wfw:commentRss><comments>http://blogs.adatis.co.uk/blogs/jeremykashel/archive/2008/12/15/audit-trail-in-performancepoint-planning.aspx#comments</comments><description>&lt;p&gt;I&amp;#39;ve noticed that the PPS Technet documentation has been updated recently to include an official Microsoft method to carry out auditing in PPS Planning.&lt;/p&gt;  &lt;p&gt;PPS will do some basic auditing out of the box, namely to the audit.log file on the server. This will automatically capture key events that occur on the server, e.g. creation of a model, updating of a dimension etc. The audit file does not, however, track changes to the model fact data. There has been a custom solution around for this for a while now - &lt;a href="http://blogs.adatis.co.uk/blogs/sachatomey/default.aspx" target="_blank"&gt;Sacha&lt;/a&gt; has written an excellent post &lt;a href="http://blogs.adatis.co.uk/blogs/sachatomey/default.aspx?PageIndex=2" target="_blank"&gt;here&lt;/a&gt; that details what you need to do in order to implement your own PPS audit trail.&lt;/p&gt;  &lt;p&gt;Like Sacha&amp;#39;s method, the Microsoft approach involves creating auditing tables, which should then be populated by running a custom stored procedure. The stored procedure should then be scheduled on a periodic basis (e.g. hourly) to capture any new activity. This is a bit different to Sacha&amp;#39;s method, where triggers are used to capture changes in real-time as they occur. In both cases the idea is to use something like Reporting Services to to view detailed auditing reports on your PPS data.&lt;/p&gt;  &lt;p&gt;One thing that did catch my eye on in the Technet documentation is a method to decode the binary &amp;#39;change list&amp;#39; column that&amp;#39;s held in the dbo.Submissions table. Whereas you can manually export the change list to a CSV file, there has historically been no way to take what&amp;#39;s in the change list column and automatically decode it into a useful format. The following C# code will read the change list, and then insert it into your newly created auditing table:&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color:teal;"&gt;DataSet &lt;/span&gt;ds = &lt;span style="color:blue;"&gt;new &lt;/span&gt;&lt;span style="color:teal;"&gt;DataSet&lt;/span&gt;();
DataLayer dl = &lt;span style="color:blue;"&gt;new &lt;/span&gt;DataLayer(&lt;span style="color:maroon;"&gt;&amp;quot;PPSConnection&amp;quot;&lt;/span&gt;);
ds = dl.ExecuteDataSetFromSQL(&lt;span style="color:maroon;"&gt;&amp;quot;SELECT [SubmissionID]FROM [_AppDB].[dbo].[Submissions] s1 where &lt;br /&gt;s1.SubmissionID not in (select SubmissionID from [_StagingDB].[dbo].[SubmissionsAudited]) &lt;br /&gt;and s1.[Status] = 0&amp;quot;&lt;/span&gt;);
&lt;span style="color:blue;"&gt;string &lt;/span&gt;sSQL = &lt;span style="color:maroon;"&gt;&amp;quot;&amp;quot;&lt;/span&gt;;

&lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="color:teal;"&gt;DataRow &lt;/span&gt;r &lt;span style="color:blue;"&gt;in &lt;/span&gt;ds.Tables[0].Rows)
{
    sSQL = &lt;span style="color:maroon;"&gt;@&amp;quot;INSERT INTO SubmissionsAudited(&amp;#8230; ) VALUES(&amp;quot;&lt;/span&gt;;

    &lt;span style="color:green;"&gt;// RETRIEVE THE CHANGELIST FOR THIS SUBMISSION 
    &lt;/span&gt;DataSetWrapper dsw = &lt;span style="color:blue;"&gt;new &lt;/span&gt;DataSetWrapper((&lt;span style="color:teal;"&gt;Byte&lt;/span&gt;[])r[&lt;span style="color:maroon;"&gt;&amp;quot;ChangeList&amp;quot;&lt;/span&gt;]);
    &lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="color:teal;"&gt;DataRow &lt;/span&gt;cldr &lt;span style="color:blue;"&gt;in &lt;/span&gt;dsw.DataSet.Tables[0].Rows)
    {
        &lt;span style="color:green;"&gt;// SUBMISSION ROW DATA
        &lt;/span&gt;sSQL += r[0].ToString() + &lt;span style="color:maroon;"&gt;&amp;quot;, &amp;quot;
        &lt;/span&gt;+ r[1].ToString() + &lt;span style="color:maroon;"&gt;&amp;quot;, &amp;quot;
        &lt;/span&gt;+ r[2].ToString() + &lt;span style="color:maroon;"&gt;&amp;quot;, &amp;quot;
        &lt;/span&gt;+ r[3].ToString() + &lt;span style="color:maroon;"&gt;&amp;quot;, &amp;#39;&amp;quot;
        &lt;/span&gt;+ r[4].ToString() + &lt;span style="color:maroon;"&gt;&amp;quot;&amp;#39;, &amp;quot;&lt;/span&gt;;

        &lt;span style="color:green;"&gt;// CHANGELIST ROW DATA
        &lt;/span&gt;&lt;span style="color:blue;"&gt;foreach &lt;/span&gt;(&lt;span style="color:blue;"&gt;object &lt;/span&gt;o &lt;span style="color:blue;"&gt;in &lt;/span&gt;cldr.ItemArray)
        {
            sSQL += &lt;span style="color:maroon;"&gt;&amp;quot;,&amp;quot; &lt;/span&gt;+ o.ToString();
        }
        sSQL += &lt;span style="color:maroon;"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;;
    }
    &lt;span style="color:green;"&gt;// STORE EACH CHANGE TO THE AUDIT TABLE
    &lt;/span&gt;dl.ExecuteNonQuery(sSQL);&lt;/pre&gt;
&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;

&lt;p&gt;Click &lt;a href="http://technet.microsoft.com/en-us/library/dd309567.aspx" target="_blank"&gt;here&lt;/a&gt; to view the Technet documentation.&lt;/p&gt;

&lt;div class="wlWriterSmartContent" id="scid:0767317B-992E-4b12-91E0-4F059A8CECA8:ab05c10d-b3ae-4d58-9e82-bde06bc7b995" style="padding-right:0px;display:inline;padding-left:0px;padding-bottom:0px;margin:0px;padding-top:0px;"&gt;Technorati Tags: &lt;a href="http://technorati.com/tags/PerformancePoint" rel="tag"&gt;PerformancePoint&lt;/a&gt;,&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,&lt;a href="http://technorati.com/tags/C#" rel="tag"&gt;C#&lt;/a&gt;&lt;/div&gt;&lt;img src="http://blogs.adatis.co.uk/aggbug.aspx?PostID=7485" width="1" height="1"&gt;</description><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/PerformancePoint/default.aspx">PerformancePoint</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/SQL/default.aspx">SQL</category><category domain="http://blogs.adatis.co.uk/blogs/jeremykashel/archive/tags/C_2300_/default.aspx">C#</category></item></channel></rss>