I would like to know how I could accomplish reusing a single instance of a ReportDocument object in an iteration instead of having to initialize and load it on each iteration.
First let me give some insight as to what I am trying to achieve. I need to generate a couple hundred account statements that are to be printed and sent out to clients. In the end all the account statements (report) are to be contained in a single pdf file to be sent to a printing company for printing and distribution.
This is the code I have for generating the reports
public static IEnumerable<ReportDocument> LoadReportData(List<Int64> accountNumbers, DateTime startDate, DateTime endDate)
{ ICollection<ReportDocument> rptDocs = new Collection<ReportDocument>(); //Fetches the data for each of the reports to be generated IEnumerable<DataSet> dsContainer = GetReportData(accountNumbers, startDate, endDate); if (dsContainer != null) { string reportFile = HttpContext.Current.Server.MapPath("~/Reports/AccountStatement.rpt"); foreach (var ds in dsContainer) { //Initilaize and load report document from file var rptDoc = new ReportDocument(); rptDoc.Load(reportFile); // set parameters SetParamValue(rptDoc, "@startDate", startDate.ToString("MMMM dd, yyyy")); SetParamValue(rptDoc, "@endDate", endDate.ToString("MMMM dd, yyyy")); rptDoc.OpenSubreport("AccountStatementDetails").SetDataSource(ds); rptDoc.OpenSubreport("AccountStatementBreakdown").SetDataSource(ds); //set the accountstatement cover parameters SetParamValue(rptDoc.Subreports["AccountStatementCover"], "@startDate", startDate.ToString("MMMM dd, yyyy")); SetParamValue(rptDoc.Subreports["AccountStatementCover"], "@endDate", endDate.ToString("MMMM dd, yyyy")); rptDoc.Subreports["AccountStatementCover"].SetDataSource(ds); //set dataset to the report viewer. rptDoc.SetDataSource(ds); rptDocs.Add(rptDoc); } } return rptDocs;
}As you can see I load the report template on each iteration and this is expensive IO operation that slows down the process.
This is what I had tried but something is going wrong as the parent function that is responsible for exporting each ReportDocument to stream generates an exception "Database logon failed"
public static IEnumerable<ReportDocument> LoadReportData(List<Int64> accountNumbers, DateTime startDate, DateTime endDate)
{ ICollection<ReportDocument> rptDocs = new Collection<ReportDocument>(); //Fetches the data for each of the reports to be generated IEnumerable<DataSet> dsContainer = GetReportData(accountNumbers, startDate, endDate); if (dsContainer != null) { //Initilaize and load report document from file string reportFile = HttpContext.Current.Server.MapPath("~/Reports/AccountStatement.rpt"); var rptDoc = new ReportDocument(); rptDoc.Load(reportFile); foreach (var ds in dsContainer) { // set parameters SetParamValue(rptDoc, "@startDate", startDate.ToString("MMMM dd, yyyy")); SetParamValue(rptDoc, "@endDate", endDate.ToString("MMMM dd, yyyy")); rptDoc.OpenSubreport("AccountStatementDetails").SetDataSource(ds); rptDoc.OpenSubreport("AccountStatementBreakdown").SetDataSource(ds); //set the accountstatement cover parameters SetParamValue(rptDoc.Subreports["AccountStatementCover"], "@startDate", startDate.ToString("MMMM dd, yyyy")); SetParamValue(rptDoc.Subreports["AccountStatementCover"], "@endDate", endDate.ToString("MMMM dd, yyyy")); rptDoc.Subreports["AccountStatementCover"].SetDataSource(ds); //set dataset to the report viewer. rptDoc.SetDataSource(ds); rptDocs.Add((ReportDocument)rptDoc.Clone()); } } return rptDocs;
}In this method (above) I initialized and loaded the report document once (before the iteration begins), set up the report params and dataset and add a clone of the actual report the the document container.
Parent function that is responsible for exporting each report to stream in order to get the byte[], which will then be used by another application to merge the collection of byte[] into a single pdf file.
public BatchAccountStatementWrapper GenerateAccountStatement(List<Int64> accountNumbers, DateTime startDate, DateTime endDate)
{ BatchAccountStatementWrapper wrapper = new BatchAccountStatementWrapper(); byte[] output = null; try { IEnumerable<ReportDocument> rptDocs = AccountStatementHelper.LoadReportData(accounts, startDate, endDate); foreach (var rptDoc in rptDocs) { using (Stream stream = rptDoc.ExportToStream(ExportFormatType.PortableDocFormat)) { var statement = new AccountStatement(); using (BinaryReader binReader = new BinaryReader(stream)) { binReader.BaseStream.Position = 0; output = binReader.ReadBytes(Convert.ToInt32(binReader.BaseStream.Length)); binReader.Close(); statement.AccountStatementData = output; wrapper.Statements.Add(statement); } //dispose of all unmanaged components rptDoc.Close(); rptDoc.Dispose(); } } } catch (Exception ex) { wrapper.ErrorCode = "SYSERR"; Logger.Log(ex, Logger.Level.Fatal); } return wrapper;
}Once I use the clone approach then the exception is generated in the ExportToStream function. Please do not get side tracked by the description of the exception message as it does not have anything to do with the database.
Just to reiterate, the aim is to reduce/eliminate the multiple IO operations required to load the report in each iteration by way of loading the ReportDocument once and reusing it in each iteration.