SQL Azure für PowerPivot und Reporting Services 2008 R2 29.10.2010

Nicolas Meseth
Nicolas Meseth, Senior eXpert

Breaking News vor dem Wochenende: Die Reporting Services sind als nächstes Microsoft Produkt in SQL Azure integriert worden. Mehr Infos gibt es im offiziellen PDC Video hier!

Zweite Neuigkeit im gleichen Zug: PowerPivot unterstützt jetzt auch SQL Azure DataMarket! Download und Infos hier!

Ein schönes Wochenende!

Share |

Mit SSAS QueryLog in File schreiben

Nicolas Meseth
Nicolas Meseth, Senior eXpert

Das QueryLog ist eine sehr nützliche Funktion des Analysis Servers, besonders wenn man seine Aggregationen Usage-basiert optimieren möchte. Der Usage-Based Aggregation Optimizer arbeitet auf Grundlage einer vom QueryLog erzeugten Datenbanktabelle.

Neben der Standardmöglichkeit, das QueryLog in eine Tabelle des SQL Servers schreiben zu lassen, kann es auch so konfiguriert werden, dass es in ein File loggt. Dafür gibt es das Property "QueryLogFileName" der SSAS Server Instanz. Leider ist in der Version 2005 das Property "QueryLog / QueryLogFileName" über das SQL Server Management Studio nicht sichtbar, d.h. es taucht nicht in der Liste der Properties auf. Es ist aber trotzdem vorhanden und kann gesetzt werden. Nur wie?

Die Lösung ist das direkte Bearbeiten der msmdsrv.ini im Config - Verzeichnis des SSAS Servers (Bei mir ist das C:\Program Files\Microsoft SQL Server\MSSQL.2\OLAP\Config). Man öffnet diese Datei mit einem Texteditor - sie ist im XML Format - und sucht nach dem QueryLogFileName - Tag. In dieses trägt man den Namen der Datei ein, in die das QueryLog schreiben soll. Zwei Dinge sind hier zu beachten:

  1. Die Datei muss die Endung .trc haben, da sie das gleiche Format wie ein Profiler Trace-File hat
  2. Die Datei muss in einem Ordner liegen, der zu den AllowedBrowsingFolders gehört

Nun einfach den Analysis Service neu starten und der QueryLog ist aktiviert. An dieser Stelle direkt der Hinweis, dass der Aggregation-Optimizer nicht mit .trc Files arbeiten kann. Warum also überhaupt in Files loggen?

In manchen Fällen ist das Schreiben in eine Datei die einzige Möglichkeit, da vielleicht keine Datenbank zur Verfügung steht, in die man das QueryLog schreiben lassen kann. Der Grund hierfür kann sein, dass das QueryLog serverweit konfiguriert wird, und somit die QueryLogs für alle SSAS Datenbanken auf diesem Server in die gleiche Tabelle geschrieben werden. Das kann in einer anwendungsorientierten Infrastruktur zu Berechtigungsproblemen führen: Welche Anwendung soll das QueryLog hosten, und wie kommen andere Andwendungen auf die entsprechende Datenbanktabelle? Dieses Problem kann man durch ein zentrales Fileshare lösen. Leider muss man in diesem Fall die Daten für die Verwendung mit dem Aggregation Optimization Wizard zunächst aus dem File wieder in eine Tabelle importieren.

Es sei ebenfalls noch erwähnt, dass das QueryLog entweder in eine Datei loggt, oder in eine Datenbanktabelle. Beides gleichzeitig funktioniert nicht.

Share |

WinForms: Aktionsausführung im Designer verhindern 27.10.2010

Matthias Jauernig
Matthias Jauernig, Senior eXpert

Erstellt man in WinForms eine Form oder ein UserControl, so können deren Properties und Elemente komfortabel über den Designer in Visual Studio angepasst werden. Wird das jeweilige Element geöffnet, so führt der Designer dabei den Konstruktor der Form aus und bezieht die darin durchgeführten Aktionen mit in die Darstellung ein.

Dieses Verhalten führt dann zu Problemen, wenn im Konstruktor z.B. Datenquellen angesprochen werden, um die UI zur Laufzeit beim Erstellen der Form bzw. des Controls aufzubauen. Zur Design-Zeit löst dies i.d.R. Exceptions aus, die ein Laden der Form/des Controls verhindern.

Der erste Versuch: DesignMode

Jede Form und jedes UserControl sind in eine Vererbungshierarchie eingebettet. Beide leiten indirekt von Control ab, das von Component erbt. Component besitzt ein boolesches Property DesignMode, über das im Konstruktor entschieden werden kann, ob die jeweilige Komponente aktuell im Designer geöffnet wird oder nicht. Dementsprechend lässt es sich dazu verwenden, um nur gewünschte Operationen zur Design-Zeit im Konstruktor auszuführen:

   1: public MyForm()
   2: {
   3:     InitializeComponent();
   4:  
   5:     // Aktionen, die immer ausgeführt werden sollen
   6:     // ...
   7:     
   8:     if (DesignMode)
   9:         return;
  10:     
  11:     // Aktionen, die nur zur Laufzeit ausgeführt werden sollen
  12:     // ...
  13: }

Das Ganze funktioniert solange, wie man die Form bzw. das Control direkt im Designer öffnen will. Gerade bei UserControls ist es aber viel häufiger der Fall, dass sie in andere Controls bzw. Formulare eingebettet sind, die man öffnen möchte. Leider greift DesignMode in diesem Fall nicht, das dann als Wert false hat. DesignMode hilft demnach nur, wenn das Control direkt im Designer geöffnet wird.

Abhilfe: Der LicenseManager

Abhilfe für dieses Problem schafft die sealed class LicenseManager aus dem Namespace System.ComponentModel. Das statische Property LicenseManager.UsageMode lässt sich dazu verwenden, um den aktuellen Darstellungsmodus auch bei eingebetteten Aufrufen ermitteln zu können. Die Lösung des Problems sieht demnach so aus:

   1: public MyForm()
   2: {
   3:     InitializeComponent();
   4:  
   5:     // Aktionen, die immer ausgeführt werden sollen
   6:     // ...
   7:     
   8:     if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
   9:         return;
  10:     
  11:     // Aktionen, die nur zur Laufzeit ausgeführt werden sollen
  12:     // ...
  13: }

Diese Lösung kann dazu verwendet werden effizient die Ausführung von Aktionen im Konstruktor zu verhindern oder beispielsweise auch um DesignTime-Daten an die UI zu binden.

Share |

Windows Live Essentials 2011 25.10.2010

Daniel Tonagel
Daniel Tonagel, Chief eXpert

Manchmal gehen bei Microsoft die Uhren etwas vor. So sind die Live Essentials 2011 bereits ab sofort kostenlos verfügbar.

Wichtig für die Profis sind Messenger, Writer, Live Mesh und das Outlook Connector Pack.

Aber auch für den privaten Gebrauch sind mit Fotogalerie, Movie Maker und Family Safety wieder nette Schmankerl dabei.

Voraussetzung für das Paket ist Windows 7 oder Vista. Unter XP laufen die neuen Anwendungen nicht.

Einen Überblick über neue (und entfallene) Features gibt es z.B. hier, einen ausführlichen Review zu einzelnen Bestandteilen hier, und schließlich noch die deutsche Pressemitteilung von Microsoft.

Viel Spaß!

Share |

Weiterer eXpert Artikel: Offende Daten für ein offenes Web 22.10.2010

Svenja Henß
Svenja Henß, Senior Assistant

Am 21. Oktober erscheint in der dotnetpro ein weiterer Artikel unseres eXperts Matthias Jauernig, diesmal zum Thema:

OData – Offene Daten für ein offenes Web

Daten im Web nicht nur über HTML verfügbar zu machen, sondern direkt und über Webadressen, die gleichzeitig Abfragen à la SQL enthalten – das Open Data Protocol macht es möglich und schafft dazu ein einheitliches Format für Webadressen.

Die dotnetpro stellt diesen Artikel kostenfrei zur Verfügung. Einfach hier auf den Link klicken: http://www.dotnetpro.de/articles/freearticles/pdf/2010-11-OData.pdf

Share |

WinForms: Multithreaded BindingList 20.10.2010

Matthias Jauernig
Matthias Jauernig, Senior eXpert

Mit einer BindingList<T> bzw. einer IBindingList kann in WinForms leicht ein Two-Way-DataBinding an ein DataGridView ermöglicht werden, indem man dessen DataSource-Property auf die BindingList setzt. Vorteil dabei: die Anzeige des DataGridView wird stets aktuell gehalten mit den Elementen in der BindingList, zusätzlich auch mit den Daten innerhalb der einzelnen Elemente, wenn diese INotifyPropertyChanged implementieren.

Doch die DataBinding-Magie hat ein Ende, wenn die BindingList in einem zweiten Thread parallel zum UI-Thread aktualisiert bzw. verändert wird. Die Änderung im zweiten Thread wird über das ListChanged-Event der BindingList an das DataGridView publiziert, das aber im UI-Thread läuft und daher ein Cross-Thread-Problem bekommt:

CrossThreadError

Fehlermeldung: "System.InvalidOperationException – Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement 'xyz' erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde."
Bzw. auf Englisch: "System.InvalidOperationException – Cross-thread operation not valid: Control 'xyz' accessed from a thread other than the thread it was created on."

Da die Aktualisierung des Grids automatisch erfolgt, hat man als Entwickler keine Chance in den Prozess einzugreifen und z.B. mit InvokeRequired zu prüfen, ob die Operation im UI-Thread stattfinden muss.

Abhilfe schafft eine eigene BindingList, die sich des Threading-Problems mit Hilfe der SynchronizationContext-Klasse annimmt und Updates so im UI-Thread auslöst. Folgende ThreadedBindingList (Modifikation einer Lösung von Stack Overflow) setzt dies um:

   1: public class ThreadedBindingList<T> : BindingList<T>
   2: {
   3:     private readonly SynchronizationContext m_syncContext = SynchronizationContext.Current;
   4:  
   5:     protected override void OnAddingNew(AddingNewEventArgs e)
   6:     {
   7:         if (m_syncContext == null)
   8:             BaseAddingNew(e);
   9:         else
  10:             m_syncContext.Send(state => BaseAddingNew(e), null);
  11:     }
  12:  
  13:     protected override void OnListChanged(ListChangedEventArgs e)
  14:     {
  15:         if (m_syncContext == null)
  16:             base.OnListChanged(e);
  17:         else
  18:             m_syncContext.Send(state => BaseListChanged(e), null);
  19:     }
  20:  
  21:     private void BaseAddingNew(AddingNewEventArgs e)
  22:     {
  23:         base.OnAddingNew(e);
  24:     }
  25:  
  26:     private void BaseListChanged(ListChangedEventArgs e)
  27:     {
  28:         base.OnListChanged(e);
  29:     }
  30: } 

Eine Instanz dieser Liste muss im UI-Thread erzeugt werden, sodass der SynchronizationContext korrekt gesetzt werden kann. Alternativ sind andere Implementierungen denkbar, die den SynchronizationContext per Konstruktor übergeben bekommen.

Share |

Versionierung von Shared Report Parts mit Sharepoint 2010 18.10.2010

Viktor Ewert
Viktor Ewert, Senior eXpert

Seit der R2 Version der Reporting Services 2008 werden Shared Reports Parts angeboten. Diese Report Parts können ein Dataset, Report-Parameter, eine Tabelle oder ein anderer Teil eines Reports sein. Die vollständige Liste der Report Parts findet man hier: Report Parts in Report Designer (SSRS)

In einer Reporting Services (Standalone) Umgebung kann jeder berechtigte User diese Report Parts mit dem Report Bilder 3.0 erstellen und verändern. Das birgt die Gefahr, dass solche Report Parts von einem User überschrieben werden und die vorherige Version nicht wiederhergestellt werden kann. Durch regelmäßige ReportServer DB Backups kann dieses Risiko verringert werden. Zudem kann man nur einen gesicherten Stand für die komplette Datenbank wiederherstellen. Wenn innerhalb eines Tages mehrfach Änderungen an verschiedenen Report Parts durchgeführt werden, stößt man an die Grenzen der Report Parts.

Installiert man die Reporting Services im Sharepoint Integrated Mode im Sharepoint 2010 kann man dessen Historisierung nutzen.

Report Part in Sharepoint

Dazu wird die Versionierung auf den den Ordner aktiviert, in denen die Parts gespeichert werden. Diese Einstellung (Versioning settings) findet man in den Document Libary Settings des Ordners.

Enable Versioning

Es sind diverse Einstellungen möglich, z.B. auch die Auswahl zwischen nur major versions oder major and minor (draft) version. Wichtig ist die Versionierung einzuschalten.

Versioning Settings

Mit Hilfe des Report Builder 3.0 kann das erstellte Report Part angepasst und als neue Version veröffentlicht werden. Schaut man sich jetzt die Versionshistorie an, so sieht man, dass eine neue Version angelegt wurde, und von wem und wann sie geändert wurde. Ebenso kann man nun eine alte Version wiederherstellen:

Drop Down Versions

Restore Report Part

Share |

Two-Way Databinding für Cascading DropDownList 15.10.2010

Matthias Malsy
Matthias Malsy, Principal Expert

“Das sollte doch einfach zu realisieren sein” war mein erster Gedanke, als die Anforderung kam, in ASP.NET zwei abhängige DropDownList Elemente in einer FormView zu implementieren.  

MSDN und Suchmaschinen liefern zudem eine hinreichende Anzahl von Beispielen zu diesem Thema. Die angebotenen Lösungen enthalten jedoch in der Regel eine Menge CodeBehind und unterstützen üblicherweise kein sauberes Two-Way Binding.

Im nachfolgenden werde ich eine Lösung mit manuellem Databinding sowie eine Lösung mit Two-Way Binding vorstellen. Beide Lösungen sind im ASP Page Lifecycle leicht nachvollziehbar und arbeiten mit einem geringen Teil an CodeBehind.

Und darum geht es:

Eine ASPX Seite besteht aus einem FormView, die einen Datensatz “Person” editiert. Der Datensatz wird durch eine DataSource bereitgestellt. In der Seite befinden sich zwei DropDownList Elemente, deren Auswahlwerte durch weitere DataSourcen bereitgestellt werden. Der Wertebereich der zweiten DropDownList “Detail-Level” ist von der Auswahl der ersten DropDownList “Master-Level” abhängig. Im Beispiel werden für den gewählten Master-Level “2” die Detail-Level “2-1” bis “2-9” angeboten.

DropDown

Die DropDowList wird mit einem Two-Way DataBinding angebunden. Das Property “SelectedValue” stellt die Verbindung her.

DropDownList Elemente

   1: <%-- gebundene DropDownList --%>
   2: <asp:DropDownList runat="server" ID="ddLevel1" AutoPostBack="true"
   3:  DataSourceId="dsLevel1"
   4:  DataTextField="Level1Name"
   5:  DataValueField="Level1Id"
   6:  SelectedValue='<%#Bind("Level1Id") 
   7:  %>'
   8:  />
   9:  
  10: <%-- gebundene DropDownList --%>
  11: <asp:DropDownList runat="server" ID="ddLevel2"
  12:  DataSourceId="dsLevel2"
  13:  DataTextField="Level2Name"
  14:  DataValueField="Level2Id"
  15:  SelectedValue='<%#Bind("Level2Id") %>'
  16:  />

Die Abhängigkeiten der DropDownList Elemente bilde ich über die DataSource ab. Diese nimmt den gewählten Wert aus dem Master-Level DropDownList mit der ID “ddLevel1” und nutzt ihn für die Datenabfrage.

DataSource Detail-Level

   1: <%-- Datenquelle DropDownList 2 in Abhängigkeit der DropDownList 1 --%>
   2: <asp:ObjectDataSource ...
   3:      SelectMethod="GetLevel2">
   4:     <SelectParameters>

5: <asp:ControlParameter Name="level1" ControlID="ddLevel1" PropertyName="SelectedValue" Type="Int32" />

   6:     </SelectParameters>
   7:  </asp:ObjectDataSource>

Leider führt diese Implementierung zu einer sehr missverständlichen Fehlermeldung.

InvalidOperationException

 

Realisierung Cascading DropDownList in einer FormView per manuellem Binding

Bei manuellem Binding der DropDown Elemente werden die SelectedValue nicht mehr gebunden. D.h. nachfolgender Code entfällt für die beiden DropDownList Elemente.

SelectedValue='<%#Bind("...") %>'

Ab jetzt muß das FormView die Verwaltung des Binding übernehmen. Bei einem Update werden die Parameter aus den DropDownList der Form bereitgestellt. Beim Databinding werden die SelecteValues gesetzt.

CodeBehind FormView

   1: protected void FormOnItemUpdating(object sender, FormViewUpdateEventArgs e)
   2: {
   3:     // Die Werte der beiden DropDownListen werden als Parameter der Datenquelle übergeben.
   4:     // Alle mit <%#Bind(...)%> versehenen Properties sind schon in den NewValues vorhanden.
   5:     var formView = sender as FormView;
   6:     var dd1 = formView.FindControl("ddLevel1") as DropDownList;
   7:     var dd2 = formView.FindControl("ddLevel2") as DropDownList;
   8:     e.NewValues["Level1Id"] = dd1.SelectedValue;
   9:     e.NewValues["Level2Id"] = dd2.SelectedValue;
  10: }
  11:  
  12: protected void FormOnDataBound(object sender, EventArgs e)
  13: {
  14:     // Nachdem das FormView gebunden wurde (d.h. alle Eval und Bind) werden manuelle die Werte der
  15:     // DropDownListen gebunden
  16:     var formView = sender as FormView;
  17:     var dd1 = formView.FindControl("ddLevel1") as DropDownList;
  18:     var dd2 = formView.FindControl("ddLevel2") as DropDownList;
  19:  
  20:     var aktuellerDatensatz = formView.DataItem as Person;
  21:     dd1.SelectedValue = aktuellerDatensatz.Level1Id.ToString();
  22:  
  23:     // Soeben wurde DropDown1 geändert. DropDown 2 muss neu geladen werden damit der selektierte Wert
  24:     // zur Verfügung steht.
  25:     dd2.DataBind();
  26:     // Jetzt kann der zweite Wert sicher gesetzt werden
  27:     dd2.SelectedValue = aktuellerDatensatz.Level2Id.ToString();
  28: }

Für die abhängige DropDownList muss zusätzlich (Zeile 26) ein DataBind aufgerufen werden, damit der zugehörige SelectedValue auch zur Verfügung steht.

 

Realisierung Cascading DropDownList in einer FormView per Two-Way Binding

Die Lösung aus dem manuellem Binding bildet auch die Grundlage für eine saubere Two-Way Binding Lösung. Nachdem das Master-DropDown Element gebunden wurde, muss die zweite DropDown manuell gebunden werden.

Der CodeBehind besteht nur noch aus einer Implementierung für DataBound der Master-DropDown

CodeBehind Master-DropDown

   1: protected void DropDown1DataBound(object sender, EventArgs e)
   2: {
   3:     // Wenn DropDown1 sein Wert ändert muß DropDown2 neu gebunden werden
   4:     var dd1 = sender as DropDownList;
   5:     var dd2 = dd1.NamingContainer.FindControl("ddLevel2");
   6:     dd2.DataBind();
   7: }

Fertig. Mehr muss nicht programmiert werden. Die ASPX Seite sieht wie nachfolgend aus:

Vollständiger Code der ASPX-Seite

   1: <body>
   2:     <form id="form1" runat="server" >
   3:     <div>
   4:     
   5:     <%-- Bearbeitung einer Person durch FormView --%>
   6:     <asp:FormView runat="server" 
   7:         ID="ctlForm" DataSourceID="dsPerson" DefaultMode="Edit" 
   8:         >
   9:         
  10:         <EditItemTemplate>
  11:             <%-- gebundenes Property --%>    
  12:             <asp:TextBox runat="server" ID="txtName" Text='<%#Bind("Name") %>'></asp:TextBox>
  13:     
  14:             <%-- ungebundene DropDownList --%>
  15:             <asp:DropDownList runat="server" ID="ddLevel1" AutoPostBack="true"
  16:              DataSourceId="dsLevel1"
  17:              DataTextField="Level1Name"
  18:              DataValueField="Level1Id"
  19:              SelectedValue='<%#Bind("Level1Id") %>'
  20:              OnDataBound="DropDown1DataBound"
  21:              />
  22:              
  23:             <%-- ungebundene DropDownList --%>
  24:             <asp:DropDownList runat="server" ID="ddLevel2"
  25:              DataSourceId="dsLevel2"
  26:              DataTextField="Level2Name"
  27:              DataValueField="Level2Id"
  28:              SelectedValue='<%#Bind("Level2Id") %>'
  29:              />
  30:  
  31:             <%-- Datenquelle DropDownList 1 --%>
  32:             <asp:ObjectDataSource 
  33:                 ID="dsLevel1" runat="server" TypeName="WebControls.MyDataSource"
  34:                 DataObjectTypeName="WebControls.Level1"
  35:                 SelectMethod="GetLevel1"
  36:                 />
  37:     
  38:             <%-- Datenquelle DropDownList 2 in Abhängigkeit der DropDownList 1 --%>
  39:             <asp:ObjectDataSource 
  40:                 ID="dsLevel2" runat="server" TypeName="WebControls.MyDataSource"
  41:                 DataObjectTypeName="WebControls.Level2"
  42:                 SelectMethod="GetLevel2">
  43:                   <SelectParameters>
  44:                     <asp:ControlParameter Name="level1" ControlID="ddLevel1"  PropertyName="SelectedValue" Type="Int32" />
  45:                   </SelectParameters>
  46:                 </asp:ObjectDataSource>
  47:  
  48:             <%-- CommandName="Update" ruft Update() auf der ObjectDataSource auf --%>
  49:             <asp:Button runat="server" ID="btnSend" Text="Senden" CommandName="Update" />
  50:         </EditItemTemplate>
  51:  
  52:     </asp:FormView>   
  53:  
  54:     <%-- Datenquelle der FormView --%> 
  55:     <asp:ObjectDataSource 
  56:         ID="dsPerson" runat="server" TypeName="WebControls.MyDataSource"
  57:         DataObjectTypeName="WebControls.Person"
  58:         SelectMethod="SelectPerson"
  59:         UpdateMethod="UpdatePerson"
  60:         >
  61:      </asp:ObjectDataSource>
  62:         
  63:     </div>
  64:     </form>
  65:  
  66: </body>

Fazit

Es gibt eine einfache Lösung für Two-Way DataBinding von Cascading DropDownList in einer FormView.

Diese besteht aus zwei exakt abgestimmten Funktionsblöcken. Zum einen muß die Detail-DataSource in Abhängigkeit des Master-DropDown Controls gesetzt werden. Zum anderen muß der DataBound Event der Master-DropDown die Detail-DropDown neu binden.

Jeder kleine Fehler in der Programmierung führt zu z.T. irreführenden Fehlermeldungen mitten aus dem Databinding heraus. Der StackTrace enthält meist kein kein “eigener” Code. Die auftretenden Fehlermeldungen sind:

  • “Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control”
  • 'DropDownList' has a SelectedValue which is invalid because it does not exist in the list of items
Share |