不用第三方之C#实现大容量报表系统
概述
现在企业中,都会有或多或少的报表来展示业务结果。实现报表有很多种方式,最简单的就是直接用纸质写出来展示,这种方式不需要任何IT系统的支持,使用比较简便,但是一般只适用比较小的企业,如果企业稍大一些,数据量增大后,这种方式就显得力不从心,随着使用的不断深入,出错的几率也会越来越大。为了解决这个问题,现在企业一般都会上马一些IT系统,如财务系统,ERP系统等,这些系统都会内置一些标准的报表,当然也有很多企业采用内部开发的方式来运作,我这里要说的就是针对这些自主开发的小型系统。
实现报表在技术上有多种选择,比如水晶报表,微软的报表服务器,及JAVA平台还有很多免费的报表工具。为了实现更灵活的结构,现在报表一般也部署为B/S结构,管理人员甚至老总只需要打开浏览器就可以直接查看一些需要的报表。这种方式非常灵活,但是当报表比较大的时候,就会出现严重的性能问题,如果提交长时间的报表,可能会造成请求中断,或后台产生过多的死进程。比如报表运算整个过程完成可能要3个小时,那么这3个小时内如果IE出现超时,或不小心关掉了浏览器,则整个报表的结果将无处寻找,但是实际上在后台依然占用大量的资源及带宽,这对应用系统的打击是很大的,如果反复多次提交,甚至会造成数据的不一致性。因此,本文档必须针对这个问题给出一个比较好解决办法。
其实提交大容量报表的最理想的方式就是在浏览器里提交一个命令,然后交给后台运算,运算完之后再交给客户下载,这样就不用担心浏览器意外断掉造成的所有的不良影响了。
设计
在Oracle EBS中,使用的就是这种工作方式,它的系统中有一个“并发管理器”,专门负责接收来自客户的请求,它的功能很强大,请求被分为很多种类型,报表仅是其中的一类,还可以做很多的事情,我们的设计相对简单,仅是为了实现大容量的报表,所以设计方面比它有所简化。
并发管理器就是一个调度程序,它首先接收客户端提交来的请求,在数据库中做一个相应的记录,然后不断的轮询,检查所有请求的状态,如果发现新的请求,就会调用相应的程序来实现这个请求,也就是调用报表程序去生成报表。
后台生成报表后,客户端如何得到呢?因为这时浏览器已经关闭了。这个不用担心,在后台生成好报表后,只要放在相应的WEB服务器的目录下,客户端就可以顺利下载了。
为了实现数据的存储,需要建立一张表,表名可以命名为:ZR_REQUEST,此表包括以下字段:
字段名 |
类型 |
PK/FK |
说明 |
REQUEST_ID |
NUMBER |
PK |
自增ID |
STATUS |
NUMBER |
请求状态 |
|
START_TIME |
DATE |
开始时间 |
|
END_TIME |
DATE |
结束时间 |
注:以上字段仅是必须的,其它一些辅助性或增强性的字段,读者可以根据需要自己添加。
在WEB服务器上,需要建两个子目录
Ø Output
存储生成的报表文件,报表文件名与请求ID号相同,这样可以根据请求号很容易找到所需要的报表文件。
Ø Log
存储生成报表过程中的日志文件,不论报表执行是否正确,都会生成日志文件,供用户选择使用。
报表文件只有最后生成后才可以下载,但是日志文件可以随着生成而随时查看运行的进度。
日志文件采用TXT类的平面文件存储即可。
下面说一下如何用程序来实现以上的设计过程。
实现
本系统采用C#来实现,数据库采用Oracle,并发管理器开发成后台服务的形式,这样可以保证服务器起动后即生效,不需要登录服务器。
并发管理器的建立很简单,使用VS2008的向导功能,建立一个后台服务程序即可,然后对程序稍做修改,部分核心代码如下:
private void WriteLog(string content)
{
StreamWriter writer = new StreamWriter(logFile,true);
writer.WriteLine(content);
writer.Close();
}
private void Start()
{
WriteLog("并发管理器起动: " + System.DateTime.Now.ToString());
tmrDaemon.Enabled = true;
}
/// <summary>
/// 设置具体的操作,以便服务可以执行它的工作。
/// </summary>
protected override void OnStart(string[] args)
{
// TODO: 在此处添加代码以启动服务。
Start();
}
/// <summary>
/// 停止此服务。
/// </summary>
protected override void OnStop()
{
// TODO: 在此处添加代码以执行停止服务所需的关闭操作。
WriteLog("并发管理器结束: " + System.DateTime.Now.ToString());
}
private void tmrDaemon_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
string sql = "select * from( select request_id from zr_request t where status=0 order by creation_time) where rownum <2";
DataAccessor data = new DataAccessor();
string result = data.ExecuteScalar(sql);
if (result != "")
{
WriteLog("开始调用请求程序,RequestID:" + result + " " + System.DateTime.Now.ToString());
Process.Start(Application.StartupPath + "\\ZRRequest.exe" ,result);
WriteLog("调用请求程序结束,RequestID:" + result + " " + System.DateTime.Now.ToString());
}
}
catch(Exception ex)
{
WriteLog(" 调用请求程序出错,因为: " + ex.Message + " " + System.DateTime.Now.ToString());
}
}
以上代码中用到了一个定时器,这个控件可以在设计界面上添加,并设定一个时间间隔,如1分钟,这个时间就是并发管理器轮询的时间,可以自己把握。
在上面的程序代码中,还可以看出生成报表是调用了一个名为ZRRequest.exe的程序,这就是本系统的主程序,它主要负责生成报表的功能。在这个程序里,生成报表的方式与常规一样,只是注意最后把生成的文件按指定的目录存放即可,并且在生成结束后,通知数据库的标记做一个更改就行了。主要的代码片断如下:
private static void GenReportOut(string reportID,string reportFileName,string outFileName,string argumentText,string outputType)
{
DataAccessor data = new DataAccessor();
ReportDocument document = new ReportDocument();
document.Load(reportFileName);
string sql = "select sql,table_name from zr_report_sqls t "
+ " where deleted = 0 and status = 1 and report_id = " + reportID;
DataTable table = data.ExecuteReader(sql).Tables[0];
foreach(DataRow row in table.Rows)
{
string tableName = row["table_name"].ToString();
sql = row["sql"].ToString();
sql = ReplaceSqlParameterValue(sql,argumentText);
DataTable tableReport = data.ExecuteReader(sql).Tables[0];
document.Database.Tables[tableName].SetDataSource(tableReport);
}
ExportOptions crExportOptions=new ExportOptions();
DiskFileDestinationOptions crDiskFileDestinationOptions=new DiskFileDestinationOptions();
crDiskFileDestinationOptions.DiskFileName=outFileName;
crExportOptions=document.ExportOptions ;
crExportOptions.DestinationOptions=crDiskFileDestinationOptions;
crExportOptions.ExportDestinationType =ExportDestinationType.DiskFile;
crExportOptions.ExportFormatType =GetExportFormatType(outputType) ; document.Export();
document.Close();
}
在以上的函数中,便实现了报表的生成及存储。当然这个函数只是一个处理过程的核心部分,必须加上外壳程序来调用它才可以,限于篇幅,在此不过多的描述程序代码,大家可以根据需求自己来开发。
总结
以上的程序仅是服务器端的后台程序,用户要实现真正的报表下载,必须开发几个B/S的页面来支持,这部分比较简单,而且和后台服务这部分内容相对独立,不再做过多的说明。
以上仅是对实现思路的一个描述,不包括所有的代码,有兴趣的朋友可以参考自己开发,并且加上自己的思路,这样才是最大的收获。
李鸣(aicken)原创 转载注明