I wanted to document this for anyone else who shows up looking for the answer:
private readonly CrystalReportViewer reportViewer = new CrystalReportViewer();
...
this.reportViewer.ReportSource = @"C:\PathToReport\Report.rpt";
using (var crystalReport = new ReportDocument())
{
foreach (DataRow row in dataSet.Tables[0].Rows)
{
var customerId = int.Parse(row["customerId"].ToString());
var isCurrent = bool.Parse(row["isCurrent"].ToString());
var totalSales = int.Parse(row["totalSales"].ToString());
// generate the report for each row
this.CreateReport(customerId, isCurrent, totalSales, crystalReport);
}
}
private void CreateReport(int customerId, bool isCurrent, int totalSales, ReportDocument crystalReport)
{
crystalReport.Load(this.reportViewer.ReportSource.ToString());
crystalReport.SetParameterValue("customerId", customerId);
crystalReport.SetParameterValue("isCurrent", isCurrent);
crystalReport.SetParameterValue("TotalSales", totalSales);
var fileName = string.Format("EndOfYear_{0}_{1}.pdf", customerId, isCurrent ? 1 : 0);
var outputPath = Path.Combine(this.txtOutputDirectory.Text, fileName);
crystalReport.ExportToDisk(ExportFormatType.PortableDocFormat, outputPath);
}
References:
CrystalDecisions.CrystalReports.Design
CrystalDecisions.CrystalReports.Engine
This code yields a filename like so:
"EndOfYear_123456_1.pdf"
It is certainly possible to generate the report object for each row, rather than passing it in, but slows things down quite a bit. Reusing the same report object doesn't have any negative impact as far as I could see, and made things go about ten times faster.
The only other thing you need is how to prepare a Crystal report, which is beyond the scope of this tutorial. Good luck!