我们已经实现了一个使用C#和ReportViewer通过LocalReport.Render方法创建PDF的过程。当我们必须处理一个大文件,比如20000多条记录时,进程就会在OutOfMemoryException中失败。我知道这个错误可能有很多原因,但我想知道下面的代码是否正常,或者您是否可以给我一个更好的解决方案。我有大约40个case语句,每个语句调用不同的.rdlc文件,这是基于类型的;由于空间不足,我没有包含所有的case语句。
USP_Select_Batch_Address_To_Sort是在执行存储过程的解决方案中定义的接受三个参数的DataSet (在本例中是批处理、运行和序列#)。
这种方法可行吗?如何改进呢?
public static void GenerateIndividualPDFs(string batch, string run, MainDataSet1 DS, MainDataSet1TableAdapters.USP_Select_Batch_Address_To_SortTableAdapter Ta, BindingSource bs, int intStart = 0, int intEnd = 0, string strLetterType = "")
{
reportDataSource.Name = "DataSet1";
reportDataSource.Value = bs;
int count;
SqlDataAdapter adapter;
var batchinfo = new clsBatchInfo();
ReportViewer report = new ReportViewer();
report.LocalReport.DataSources.Add(reportDataSource);
if (!Directory.Exists(mstrFilePath + "\\" + batch + run))
{
DirectoryInfo di = Directory.CreateDirectory(mstrFilePath + "\\" + batch + run);
}
var DA = new clsGetBatchSort();
//this dataAdapter will print the full set
adapter = new SqlDataAdapter(DA.dsBatch_Sort(batch, run));
DataSet ds = new DataSet();
adapter.Fill(ds);
count = ds.Tables[0].Rows.Count;
int i = 0;
int LetterTypeID = 0;
report.ProcessingMode = ProcessingMode.Local;
foreach (DataRow dr in ds.Tables[0].Rows)
{
i++;
report.Clear();
Ta.ClearBeforeFill = true;
try
{
LetterTypeID = Convert.ToInt32(dr[2]);
switch (LetterTypeID)
{
case 1:
{
Ta.Fill(DS.USP_Select_Batch_Address_To_Sort, Convert.ToDecimal(batch), run, Convert.ToInt32(dr["MPresortID"]));
report.LocalReport.ReportEmbeddedResource = "ReportsApplication1.ABC.MAENG.rdlc";
ExportReport(Convert.ToInt32(dr["MPresortID"]), batch, run, report);
continue;
}
case 2:
{
Ta.Fill(DS.USP_Select_Batch_Address_To_Sort, Convert.ToDecimal(batch), run, Convert.ToInt32(dr["MPresortID"]));
report.LocalReport.ReportEmbeddedResource = "ReportsApplication1.ABC.MASPA.rdlc";
ExportReport(Convert.ToInt32(dr["MPresortID"]), batch, run, report);
continue;
}
catch (Exception ex)
{
clsEmail.EmailMessage("Error Batch " + batch + run, "clsGenerateLetters " + ex.Message);
continue;
//MessageBox.Show(ex.Message);
//return "";
}
}
}
private static void ExportReport(int PreSortID, string batch, string run, ReportViewer Report)
{
Report.RefreshReport();
Warning[] warnings;
string[] streamids;
string mimeType;
string encoding;
string filenameExtension;
string fmt = "00000000";
byte[] bytes = Report.LocalReport.Render(
"PDF", null, out mimeType, out encoding, out filenameExtension,
out streamids, out warnings);
string filename = mstrFilePath + "\\" + batch + run + "\\" + PreSortID.ToString(fmt) + ".PDF";
using (FileStream fs = new FileStream(filename, FileMode.Create))
{
fs.Write(bytes, 0, bytes.Length);
fs.Close();
}
Report.LocalReport.ReleaseSandboxAppDomain();
}发布于 2014-08-20 19:46:12
我可能无法用内存来解决您的问题,但是我会说您正在从ADO将一个大的DataSet加载到内存中,然后您就不是Dispose-ing DataSet了。这将不会释放与DataSet相关的资源(它不一定会在超出范围后被释放或成为GCed ),这可能是一个促成因素。
同样值得注意的是,DataSet的性能比IEnumerable差。我知道这是有原因的,但我不能指手画脚,我不想通过解释错误的原因来损害你的利益,但你可以看看来问个问题在这两者之间的表现。
您可以在使用DataSet从SQL检索IEnumerable后将其转换为Cast<T>。在此之后,您可以在IEnumerable上调用LINQ理解和运算符。这将更快,而且也会被延迟评估--这意味着您可以在IEnumerable上构建一个查询并从您的方法中返回它,并且它将永远不会被迭代,直到您真正需要访问元素。欢庆!更少的计算能力和更少的内存需求。
这也有一个额外的好处,防止您不得不从一个DataTable复制到另一个(这是足够沉重的)。
另一个提示--不要让这个方法返回一个空值。你的方法现在做的太多了。你应该把它分成多个方法,就像这样。
// Where StronglyTypedRepresentation is a type that holds all of the fields
// you expect to receive back from the stored procedure
// uspSelectBatchAddressToSortTableAdapter
ReportViewer GeneratePdf(IEnumerable<StronglyTypedRepresentation> data) {}
IEnumerable<StronglyTypedRepresentation> MapSqlResult(DataSet data) {}
void SavePdf(ReportViewer reportViewer) {}请记住,每个方法(/ type)只应该做一件事情。
因此,概括地说:
IEnumerable将初始数据从ADO转换为Cast<T>。LINQ的理解几乎总是更快(需要引用--我只有轶事证据可以提供)。如果需要,始终可以将需要发送到存储过程的内容转换回datatable (如果需要将所有内容转换为存储过程,那么我建议在数据库端执行此计算,因为这是为执行批处理操作而优化的)https://codereview.stackexchange.com/questions/60611
复制相似问题