Die Reporting Features vom neuen SQL-Server 2005 finde ich als AdHoc Lösung ein schneller und eleganter Weg, um Daten anspruchsvoll zu präsentieren UND auch noch einfach druckbar oder nach PDF konvertierbar zu machen. Wir setzten in vielen Projekten schon die Reporing Service ein, wobei ich festgestellt habe, das die der Einsatz von lokal gerenderten Reports ebenso einfach wie elegant eingsetzt werden kann. Lokale-Reports können mit dem Visual Studio 2005 auch ohne SQL-Reporting Service erzeugt und gerendert werden. Der einzige Nachteil dabei ist, dass man sich selber um die Datenquellen und Daten kümmern muß. Da ich diese Möglichkeit in einem aktuellen Projekt eingesetzt habe und einige Fallstricke bei der Benutzung des ReportViewer-Control festgestellt habe, wird ich mal einige Punkte hier beleuchten:
SampleReport.rdl = SampleReport.rdlc
Auch wenn in einem VS-Projekt die beiden Reportarten mit unterschiedlichen File-Extensions aufgelistet werden, sind die Dateiformat kompatibel. D.h. Wenn man ein Server-Report *.rdl in ein lokalen – Report umwandeln möchte, muss man nur das File von *.rdl in *.rdlc umbenennen. Dadurch gewinnt man auch noch den XML-Konten für das SQL-Sequenz, um die Daten für diesen Report zu ermitteln. Das habe ich mir zum nutzen gemacht, um die Reports einfach mit dem Server-Report Designer zu erstellen (was im übrigen dann sehr elegant ist, da man den Report inkl. SQL-Statement konfigurieren kann) und dann in das entsprechende Projekt als *.rdlc rein kopieren. Mit dem folgenden Code kann man dann so einen Report einfach lokal laden und render inkl. Datenermittlung:
private void LoadReportData(Dictionary<string, object> myParamValues)
{
// Daten laden
string mySQL = "";
XmlDocument myReportXML = new XmlDocument();
myReportXML.Load(this.reportViewer1.LocalReport.ReportPath);
XmlNodeList myList = myReportXML.GetElementsByTagName("DataSet");
this.reportViewer1.LocalReport.DataSources.Clear();
foreach (XmlNode myDSNode in myList)
{
DataTable myDataTable = new DataTable(myDSNode.Attributes["Name"].Value);
XmlNode myQuery = FindXmlNode(myDSNode, "Query");
if (myQuery != null)
{
XmlNode myCmd = FindXmlNode(myQuery, "CommandText");
XmlNode myDSName = FindXmlNode(myQuery, "DataSourceName");
SqlDatabase myDB = new SqlDatabase(ConfigurationManager.ConnectionStrings["mySQLConnectionString"].ConnectionString);
SqlCommand myCmd = new SqlCommand(sSql);
if (myParams != null)
{
foreach (string myParam in myParams.Keys)
myCmd.Parameters.Add(new SqlParameter(myParam, myParams[myParam]));
}
DataSet myDS = myDB.ExecuteDataSet(myCmd);
if (myDS.Tables.Count > 0)
myDataTable =myDS.Tables[0]);
else
myDataTable = new DataTable();
ReportDataSource myData = new ReportDataSource(myDSName.InnerText, myDataTable);
this.reportViewer1.LocalReport.DataSources.Add(myData);
}
}
this.reportViewer1.RefreshReport();
this.reportViewer1.Update();
}
Diese Funktion holt nun aus der *.rdlc XML-Datei das SQL-Statement und führt es, wobei dieser Funktion auch noch ein Array mit Eingabeparametern übergeben werden kann.
Parameter-Eingabe bei lokalen Reports im ReportViewer-Control
Die Parameter-Definition von Reports ist leider bei den lokal gerenderten reports nicht so vollständig implementiert wie bei den serverseitigen Reports. Somit stellt sich das ReportViewer-Control auch im Bereich der Parametr anders dar. Zum Leid der Entwickler muss man sich für die Paramtereingabe eine eingene Lösung bauen, da das Parameter-Panel mit der dynamisch generierten Eingabecontrols für lokale Reports nicht zur Verfügung steht.
Ich habe mir dazu einfach ein Usercontrol gebaut, dem ich einfach per Methode ein Array der ReportParameter übergebe und dieses dann die Eingabecontrols dynmisch generiert:
public void SetParamWindow( ReportParameterInfoCollection myParams )
Dieses Control habe ich dann in meinem eigenen Report-Viewer Control, welches aus einem Split-Container mit zwei Panels besteht und dort einmal mein Parameter-Control und auf der anderen Seite das ReportViewer-Control befinden. Die Parameter werden dann wie folgt an das Parameter-Control übergeben:
// prüfe ob Report mit Parameter?
ReportParameterInfoCollection myParams = reportViewer1.LocalReport.GetParameters();
if (myParams != null && myParams.Count > 0)
{
splitContainer2.Panel1Collapsed = false;
ucReportParameters1.SetParamWindow(myParams);
}
else
{
splitContainer2.Panel1Collapsed = true;
LoadReportData(null);
}
Reports dynamisch dem ReportViewer-Control zuweisen
Nach den o.b. Hürden war ich nun überglücklich lokal gerenderte Reports einfach wie serverseitige Reports zu designen und im ReportViewer-Control auch genau so darzustellen. Nun lässt die Methode this.reportViewer1.RefreshReport()darauf schlissen, dass mit der Kombination des Properties this.reportViewer1.LocalReport.ReportPath (welches als ein Getter und ein Setter besitzt) dem ReportVIewer-Control auch zur Laufzeit einmal einen neuen Report zur Darstellung mitgeben kann. Leider hat sich nach einigen frustrierten Test ergeben, das dieses nicht so funktioniert. Entweder ist es ein Feature oder ein Bug! Da ich diese Funktionalität allerdings unbedingt benötigte habe ich mir ein Workaround (mal wieder) gebaut. Der einfachste Trick der mir dazu eingefallen war ist, das anscheint dieses Property nur beim Erzeugen des Controls ausgewertet wird. Somit habe ich in mein Panel vom Split-Container, welches das ReportViewer-Control beinhalttet, einfach zum Zeitpunkt des Neuladen eine neue Instanz des ReportViewer-Control erzeugt habe.
Somit ergibt sich eine Lade-Methoide für neue Reports wie folgt:
private void LoadReportByClient(string sPath)
{
this.splitContainer2.Panel2.Controls.Clear();
this.reportViewer1 = new Microsoft.Reporting.WinForms.ReportViewer();
this.reportViewer1.Dock = System.Windows.Forms.DockStyle.Fill;
this.reportViewer1.Location = new System.Drawing.Point(0, 0);
this.reportViewer1.Name = "reportViewer1";
this.reportViewer1.Size = new System.Drawing.Size(454, 366);
this.reportViewer1.TabIndex = 0;
this.splitContainer2.Panel2.Controls.Add(this.reportViewer1);
this.reportViewer1.LocalReport.ReportPath = sPath;
// prüfe ob Report mit Parameter?
ReportParameterInfoCollection myParams = reportViewer1.LocalReport.GetParameters();
if (myParams != null && myParams.Count > 0)
{
splitContainer2.Panel1Collapsed = false;
ucReportParameters1.SetParamWindow(myParams);
}
else
{
splitContainer2.Panel1Collapsed = true;
LoadReportData(null);
}
this.reportViewer1.LocalReport.ReportPath
}