Creating Subreports Using The ReportViewer
Creating Subreports Using The ReportViewer
By Dennis E. Rehm
You ve designed a great interactive Web application now users want reports they can print, save, or e-mail. It s a rare Web application that doesn t have some reporting component to it. We re going to look at a Microsoft solution and use the built-in Visual Studio Report Designer and its control, the ReportViewer. If your Web application is hosted by a service, they may not support third-party products, but they probably will support the ReportViewer.
We will create an advanced report that includes two subreports. The Visual Studio Report Designer and ReportViewer are included in all versions of Visual Studio 2005, except Express. (If you have Visual Web Developer 2005 Express, you can download the Report Designer and ReportViewer as part of a feature pack for SQL Server 2005 athttp://www.microsoft.com/downloads/details.aspx?FamilyID=d09c1d60-a13c4479-9b91-9e8b9d835cdc&displaylang=en. Scroll to the bottom of the page and look for SQLServer2005_ReportAddin.msi.) The interface for the Report Designer is similar to Reporting Services.
We ll first create a report and then embed two subreports. We ll do this using the AdventureWorks OLTP database. (The AdventureWorks database can be downloaded from CodePlex at http://www.codeplex.com/MSFTDBProdSamples. Installation instructions are included.)
The process of creating functioning subreports requires meticulous attention to detail. If you do something wrong, your only indication may be the message Error: Subreport could not be shown. There are few diagnostics to help you pinpoint and resolve the
problem. If you encounter this message, please review each step carefully. In the worst case, simply delete the objects and start over.
We ll be using the following three tables from the AdventureWorks database: Product, ProductCostHistory, and ProductListPriceHistory. See Figure 1 for the data model. Product has a one-to-many relationship with the other two tables, although not all products have cost and list-price history. Marketing has requested a report that shows each product with its cost and list-price history. We re going to build that report together.
Figure 1: Data model for the Product, ProductCostHistory, and ProductListPriceHistory tables.
Click the Next button. You ll be asked to save the connection string to the configuration file. You should do so. The default name is fine. Next, choose a command type. Select the Use SQL Statements radio button (see Figure 4). For this example, we ll use SQL statements to retrieve the data. In a production system I prefer stored procedures, which can be granted permissions independent of the underlying tables (but using SQL statements is simpler for our purposes here). Click the Next button.
Click the Advanced Options button and uncheck the box Generate Insert, Update and Delete statements (see Figure 5). Because this is a report, no data modification statements will be required. Click the OK button to close that window and then click the Next button.
The wizard will display a summary of the items to be generated. Click the Finish button. Your dataset should look like the one in Figure 7. Save and close your dataset. You ve now created the master dataset that will retrieve the data to drive the report.
The Report Designer has a panel labeled Website Data Sources. If it s not displayed for you, go to the menu at the top of the window and select the Data menu option. Under that is a single menu item: Show Data Sources; select it and the Website Data Sources panel will be displayed. If it s not docked, you can dock it. The lower left corner is its usual home. Our ProductHistoryRpt dataset is displayed as a Website Data Source. Expand Product and you can see the data elements we selected in our SQL statement (see Figure 8).
You can make the ReportViewer control wider and taller as needed for the report. See Figure 10 for my Web page with the controls on it. We are now ready to test the basic report. Run the Web application and go to the Web page with the report. It may be easiest if you set the report Web page as the Start Page. Because the SQL statement for our ProductHistoryRpt dataset has no criteria, it will be executed as soon as the page is built. You should see the Web page with the report populated with data (see Figure 11 for an example).
Figure 11: The Web page with the report populated with data.
If you have errors connecting to the database, open web.config and verify your connection string. Be sure to use your server name and the User ID and Password you have for the AdventureWorks database: <connectionStrings> <add name="AdventureWorksConnectionString" connectionString="Data Source=YourServerName; Initial Catalog=AdventureWorks; Integrated Security=False; User ID=ReportApp;Password=reportapp" providerName="System.Data.SqlClient"/> </connectionStrings>
You must have the basic report working in order to continue. When you have your report displaying in the Web page, you are ready to add your first subreport.
report parameter should look like Figure 14. Click the OK button, save the report, and close it.
Figure 14: The report parameter. Adding the First Subreport to the Report
Open the master report, ProductHistory.rdlc. We are using a Table control to display the data in our master report. We ll need to enhance the Table control to add a subreport. First, we need a second Details row. Click on the Table control so the borders are visible. Right-click on the Details row descriptor and select Insert Row Below. A second Details row will be displayed. This row has five columns just like the row above it. We want to merge the four columns on the right, the columns for Product Number, Color, Standard Cost, and List Price. Click in the second Details row under Product Number and, holding the mouse button down, slide your cursor over the three columns to the right. When you release the mouse button, the four right-most columns in the new, second Details row should be selected. Right-click within the selected area and select Merge Cells. Now we have one wide column perfect for a subreport. (We re using the four right columns so the subreport is indented from the Product information above it.)
Drag a subreport control to the new single merged cell. The cell will turn gray. Rightclick the subreport and select Properties from the pop-up menu. If the General tab is not selected, click it to select it. Enter CostHistory as the name and select ProductCostHistory in the Subreport dropdown list (see Figure 15). Click the Parameters tab. Under Parameter Name, type ProductID; under Parameter Value, select =Fields!ProductID.Value from the dropdown list (see Figure 16). This will map the cost history report parameter to the data column in the master report.
Figure 16: Map the cost history report parameter to the data column in the master report.
The report now has the cost history subreport in place. To identify the subreport as cost history, enter Cost History: in the table control cell under the Product Name (see Figure 17). Save the report and close the Report Designer.
We want to give this ObjectDataSource a more descriptive ID. Select it and in the Properties panel, change the ID to objProductCost. The Web page now has two ObjectDataSources, one for the original master report and one for the subreport (see Figure 18).
With the Cost History subreport embedded in our master report, and with an ObjectDataSource in place to retrieve the cost history data, we are ready to wire things together. Go to the code for the Web page. Add an Imports statement for Reporting:
VB Imports Microsoft.Reporting.WebForms
C# using Microsoft.Reporting.WebForms;
In the Load event for the page, create an event handler to process the subreport. This code should be executed every time (whether the page is a postback or not):
We ve created an event handler name by concatenating the name of the control and the name of the event. In this case, it s ReportViewer1_SubreportProcessing.
In the ReportViewer1_SubreportProcessing event, we ll link the parameter from the cost history SQL statement with the Report Parameters defined for the cost history subreport within the master report. This event will be fired once for each master row. In this case, the Product ID will be passed to the SQL statement to retrieve cost history rows for that Product ID (see Figure 19).
VB Protected Sub ReportViewer1_SubreportProcessing _ (ByVal sender As Object, _ ByVal e As SubreportProcessingEventArgs) Try objProductCost.SelectParameters("ProductID").DefaultValue = _ e.Parameters("ProductID").Values(0) Catch ex As Exception lblMsg.Text = ex.Message End Try e.DataSources.Add(New ReportDataSource _ ("ProductCostHistoryRpt_ProductCostHistory", _ objProductCost.ID)) End Sub
C# protected void ReportViewer1_SubreportProcessing (object sender, SubreportProcessingEventArgs e) { try { objProductCost.SelectParameters["ProductID"].DefaultValue = e.Parameters["ProductID"].Values[0]; } catch (Exception ex) { lblMsg.Text = ex.Message; } e.DataSources.Add(new ReportDataSource ("ProductCostHistoryRpt_ProductCostHistory", objProductCost.ID)); } Figure 19: Product ID passed to the SQL statement to retrieve cost history rows for that Product ID.
Note the name of the ReportDataSource in the last statement. It is ProductCostHistoryRpt_ProductCostHistory , which is the name of the dataset concatenated with the name of the datatable. We looked at this in the earlier section, Displaying the Report. This is important to understand as you create your own subreports. Because it is a literal, IntelliSense cannot help you.
At this point, if you are using Visual Basic, your code for the Web page should look like the code in Figure 20. When you run the application and open the Web page, the page should look like Figure 21. If it doesn t, carefully review all the steps regarding the Cost History subreport. When it is working, we re ready to add a second subreport. This will go more quickly.
Figure 20: Code for the Web page using Visual Basic.
Figure 21: Run the application and open the Web page.
SELECT ProductID, StartDate, EndDate, ListPrice, ModifiedDate FROM Production.ProductListPriceHistory WHERE ProductID = @ProductID ORDER BY StartDate DESC
Remember to uncheck the options that would generate additional SQL statements and database methods. If you re having problems creating this dataset, follow the step-bystep instructions in the earlier section, Creating the Master Dataset.
Drag a Table control to the report and drag the StartDate, EndDate, ListPrice, and ModifiedDate columns from the ProductListPriceHistoryRpt dataset in the Website Data Sources to the report columns in the Table control on the report. You ll need to add a column as we did for the cost history report. You also can remove the Footer row. Once again, we are not using the ProductID in the report.
I used an 8 pt. font for the report, a dark gray background for the heading, and a white foreground. This creates a divider when viewing the subreport within the report. Click on each of the three dates and set the Format property in the Properties panel to d. Click on the List Price data element and set the Format property to c.
Right-click in the Report Designer, but not in the report. Select Report Parameters from the pop-up menu. Add a report parameter for the SQL statement, which we named @ProductID. Again, we ll name our report parameter ProductID. The parameter needs to be an integer. Click the OK button, save the report, and close it.
Drag a subreport control to the new single merged cell. The cell will turn gray. Rightclick the subreport and select Properties from the pop-up menu. On the General tab enter ListPriceHistory as the name and select ProductListPriceHistory in the Subreport dropdown list. Click the Parameters tab. Under Parameter Name type ProductID and under Parameter Value select =Fields!ProductID.Value from the dropdown list.
The report now has the list-price history subreport in place. Add List Price History: in the cell to the left of the List Price History subreport. See Figure 22 for the completed report. Save the report and close the Report Designer.
Rename the ObjectDataSource ID to objProductListPrice. The Web page now has three ObjectDataSources, one for the original master report and one for each of the subreports.
Because we have one subreport on our Web page, our code will need to differentiate between the subreports. We ll do this in the ReportViewer1_SubreportProcessing
event. The ReportPath allows us to identify which subreport we are processing (see Figure 23).
VB Try If e.ReportPath = "ProductCostHistory" Then objProductCost.SelectParameters("ProductID").DefaultValue = _ e.Parameters("ProductID").Values(0) e.DataSources.Add(New ReportDataSource _ ("ProductCostHistoryRpt_ProductCostHistory", _ objProductCost.ID)) ElseIf e.ReportPath = "ProductListPriceHistory" Then objProductListPrice.SelectParameters("ProductID").DefaultValue = _ e.Parameters("ProductID").Values(0) e.DataSources.Add(New ReportDataSource _ ("ProductListPriceHistoryRpt_ProductListPriceHistory", _ objProductListPrice.ID)) End If Catch ex As Exception lblMsg.Text = ex.Message End Try
C# try { if (e.ReportPath == "ProductCostHistory") { objProductCost.SelectParameters["ProductID"].DefaultValue = e.Parameters["ProductID"].Values[0]; e.DataSources.Add(new ReportDataSource ("ProductCostHistoryRpt_ProductCostHistory", objProductCost.ID)); } else if (e.ReportPath == "ProductListPriceHistory") { objProductListPrice.SelectParameters["ProductID"].DefaultValue = e.Parameters["ProductID"].Values[0]; e.DataSources.Add(new ReportDataSource ("ProductListPriceHistoryRpt_ProductListPriceHistory", objProductListPrice.ID)); } } catch (Exception ex) { lblMsg.Text = ex.Message; } } }
If you ve done everything right, when you run the application and open the Web page, the page should look like Figure 24. Congratulations! You ve created a report with two embedded subreports. If the report doesn t work properly, carefully review the steps to add the List Price History subreport.
Conclusion
The Visual Studio Report Designer and the Web page ReportViewer contain a lot of power. Getting access to that power requires a little code and some attention to detail. We could add selection criteria to the Web page and allow the user to define the products they d like to see. There are other enhancements we could make, but this will get you started with subreports.
Dennis E. Rehm has been developing software using different languages and tools for 30 years. He s still looking for the silver bullet. He has his own company, Applied Computing, where he does teaching, consulting, and software construction for government, education, and Fortune 500 businesses. You can reach Dennis atmailto:DennisRehm@AppliedComputing.net.
Working with RDLC and passing parameter to subreport in Report Viewer control
Selvar 30 Aug 2009 3:00 PM
9 Recently I was looking for a sample that will help me with the concept of Passing parameters to sub reports in RDLC. I didnt get one. So I thought of exploring more on to this and started off the journey. The tricky part with the RDLC is, even though it gives you the option of creating report parameters, you cannot assign values from a query. More over you cannot make the parameter prompts visible as you do in RDL. So the trick here is, the person who is designing the report should use the ASP.NET controls for parameters and pass it along with the query that youre using it for filing your report data set. The other way is to define parameters, pass the values to parameters from the ASP.NET control to the Report Viewers Local report parameter collection and then just create filters in the table / matrix / tablix. But the problem here is, your performance of the report can be impacted as the query might bring large number of data for each request (based on the query). Ill be explaining both the concepts. But lets get started with the passing the parameters from the ASP.NET controls to the query and get the filtered data for the report data set. In our sample well be using ADVENTUREWORKS database. The tables which Ive selected are Department and EmployeeDepartmentHistory. The objective here is, the main report REPORT1.rdlc will show the Department table details and contains a sub report component with REPORT2.rdlc which displays EmployeeDepartmentHistory.
All the data are based on the parameters that we choose in the ASP.NET page which contains the ReportViewer control. 1. Open VS 2005 / 2008 and create a ASP.NET web application. (Choose either VB.NET / C#. Ill be talking from C# perpective.) 2. Add a new report item called REPORT1.RDLC. 3. In the Website Data Sources, click on the Add New Datasource icon. 4. Leave the dataset name as Dataset1. 5. Point the connection to the appropriate SQL Server and map it with the Department table. 6. Create another new datasource (step 3) and name it as Dataset2. 7. Map it to the same SQL Server as in step 5 and map it with the table EmployeeDepartmentHistory table. 8. Now from the toolbox drag and drop a table component in to REPORT1. 9. From Dataset1, drag and drop the DepartmentID and DepartmentName columns in to the report table. 10. Now goto the solution explorer and add a new item, REPORT2.RDLC. 11. Drag and drop a table component in to REPORT2. Then drag the columns DepartmentID, ShiftID and ShiftDate. 12. Then add a group based on DepartmentID which displays the DepartmentID in the Group Header.
13. Go back to REPORT1.rdlc and drag a sub report component from the toolbox. Point the sub report to REPORT2.rdlc. 14. With this we completed the design of the Dataset1, Dataset2, Report1.rdlc and Report2.rdlc. Now lets design the ASP.NET page. 15. Switch to the Default.aspx page design layout. There youll already find Objectdatasource1 and Objectdatasource2.
16. Drag and drop a Listbox control, ListBox1 (For multi value parameters). 17. Go to the properties of the ListBox1 (Click on the > icon that appears to the top right on the control) and click on Choose datasource link. 18. Choose the following, Datasource: ObjectDataSource1 Data field for display : Name Data field for Value : DepartmentID 19. Make sure the selection mode for the ListBox1 is set to Multiple. 20. Drag and drop the report viewer control, ReportViewer1 from the tool box. 21 . Go to the properties of the ReportViewer1 (Click on the > icon that appears to the top right on the control) and choose Report1.rdlc from Choose Report drop down. 22. Now drag a button control, btnView adjacent to the ListBox1. This brings us to the end of the design phase. Lets step in to the coding part. The coding part needs to be done little carefully. The reason, it has to follow a specific order to get the report properly. The sequence is as follows,
1. 2.
Get the list of parameters selected. Build the SQL query with the parameters and get the result in to the corresponding datasets. 3. Bind the datasets back to the reports (Main and sub report). 4. Refresh the control to show the updated report.
Lets have a look at the code within the PageLoad event of the default.aspx page. Inline Comments are provided to explain the code pieces.
protected void Page_Load(object sender, EventArgs e) { //Eveytime you add a data source to the report, it adds itself in to //a collection. Reason, report allows more than one data source to be //used within itself.Unless you specify the data source with index, //DataSources[0] will be taken automatically since our report has only //one data source. In our case for every refresh we re-execute the //query and get
different result set. So to make sure we keep the //current dataset as default and to prevent data sets getting bulged //up, were making sure to remove the last available data set and then //add the current data set.
//Adding the newly filled data set to the report. After this the Data //Source count will show 1. The first parameter is the name of the data //set bound with the report** and the next is the call to method which //will execute the QUERY and return a data table that contains the //current data.
//For the sub report to be processed, we need to add the sub report //processing handler to the main report and that is what is shown //below. The data set processing definition for the sub report will be //defined with in that method.
this.ReportViewer1.LocalReport.SubreportProcessing += newSubreportProcessingEventHandler(localReport_SubreportProcessing); }
Lets have a look at the SubreportProcessing event handler: //All weve done here is, were binding the data set to appropriate report data source**.
void localReport_SubreportProcessing(object sender, SubreportProcessingEv entArgse) {
** To view the name of the data set (i.e DataSet1_Department) Go to the Report1.rdlc -> Report menu -> Data Sources.. -> Under Report Data Sources, youll see the list of data sources, if the report contains more than one. Now were going to look at the method GetEmployeeData () that fetches the data to the data set DataSet2_EmployeeDepartmentHistory for Report2.rdlc / ObjectDataSource2.
private DataTable GetEmployeeData() { //Since string is immutable, were using StringBuilder as well be //changing the data frequently based on the parameter selection.
//We need to get the selected parameters seperated by , and enclosed //within the (i.e 1,3,7). This is to make sure weve the //proper list of multi value parameters.
//Just initializing the parameter values with a default value. This //will come in to picture only during the first exectution as we need //to show some data as soon as the report loads for the first time.
if (param.Length == 0) { paramvalues = defaultValue; } else { //Making sure the last , which got added in the above foreach loop is //removed before the values are passed to the queries.
paramvalues = param.ToString().Remove(param.ToString().LastIndexOf(",")).ToString(); }
//Creating a new SQL server connection object. using (SqlConnection sqlConn = new SqlConnection("Data Source=localhost;Initial Catalog=Adventureworks;Integrated Security=SSPI")) { //Creating a new SQL Adapter along with the SQL query and the //connection. SqlDataAdapter adap = new SqlDataAdapter("SELECT EmployeeID, DepartmentID, ShiftID, StartDate, EndDate, ModifiedDate FROM HumanResources.EmployeeDepartmentHistory where DepartmentID IN (" + paramvalues + ")", sqlConn);
//Create the typed data set of EmployeeDepartmentHistory and fill it //with the data. DataSet2 ds = new DataSet2();
adap.Fill(ds, "EmployeeDepartmentHistory");
Lets look at the method GetDepartmentData () that fetches the data to the data set DataSet1_Department for Report1.rdlc / ObjectDataSource1.
private DataTable GetDepartmentData() { //Since string is immutable, were using StringBuilder as well be //changing the data frequently based on the parameter selection.
//We need to get the selected parameters seperated by , and enclosed //within the (i.e 1,3,7). This is to make sure weve the //proper list of multi value parameters.
//Just initializing the parameter values with a default value. This //will come in to picture only during the first exectution as we need //to show some data as soon as the report loads for the first time.
if (param.Length == 0) { paramvalues = defaultValue; } else { //Making sure the last , which got added in the above foreach loop is //removed before the values are passed to the queries.
paramvalues = param.ToString().Remove(param.ToString().LastIndexOf(",")).ToString(); }
using (SqlConnection sqlConn = new SqlConnection("Data Source=Localhost;Initial Catalog=Adventureworks;Integrated Security=SSPI")) { //Creating a new SQL Adapter along with the SQL query and the //connection.
SqlDataAdapter adap = new SqlDataAdapter("SELECT DISTINCT DepartmentID, Name FROM HumanResources.Department where DepartmentID IN (" + paramvalues + ")ORDER BY DepartmentID", sqlConn);
adap.Fill(ds, "Department");
return ds.Department; } }
Finally, whenver whenever we click on the View Report button, the query has to be executed with the new set of parameters and then the entire above operation as to be executed. The below code invokes it.
protected void btnView_Click(object sender, EventArgs e) { this.ReportViewer1.LocalReport.Refresh(); } That pretty much concludes our sample. Ive uploaded the sample please change the connections string values and try executing the report. Happy programming!!.