不用第三方之C#实现大容量报表系统

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

概述

    现在企业中,都会有或多或少的报表来展示业务结果。实现报表有很多种方式,最简单的就是直接用纸质写出来展示,这种方式不需要任何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(李鸣)博客园博客,原文链接:http://www.cnblogs.com/isline/archive/2010/04/14/1711655.html,如需转载请自行联系原作者

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
存储 开发框架 .NET
C#语言如何搭建分布式文件存储系统
C#语言如何搭建分布式文件存储系统
84 2
|
2月前
|
存储 分布式计算 监控
C# 创建一个分布式文件存储系统需要怎么设计??
C# 创建一个分布式文件存储系统需要怎么设计??
41 0
|
5月前
|
存储 Oracle 关系型数据库
PACS源码,C#语言数字医学影像系统成品源码
**数字医学影像系统(RIS/PACS)**采用C#开发,基于C/S架构,配Oracle数据库,具备自主版权,适用于项目实施。系统包含分诊、超声、放射、内镜、病理等工作站,支持基本信息维护、报表查询和系统维护。功能亮点有:WorkList管理、影像采集传输、存储检索、图像处理、多序列浏览、流程控制、报告录入与审核、支持多种影像设备及高级影像处理。RIS与PACS数据库同步,并集成HIS、电子病历等系统接口。全面遵循DICOM3.0标准。
PACS源码,C#语言数字医学影像系统成品源码
|
6月前
|
开发框架 前端开发 .NET
LIMS(实验室)信息管理系统源码、有哪些应用领域?采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码
集成于VS 2019,EXT.NET前端和ASP.NET后端,搭配MSSQL 2018数据库。系统覆盖样品管理、数据分析、报表和项目管理等实验室全流程。应用广泛,包括生产质检(如石化、制药)、环保监测、试验研究等领域。随着技术发展,现代LIMS还融合了临床、电子实验室笔记本和SaaS等功能,以满足复杂多样的实验室管理需求。
83 3
LIMS(实验室)信息管理系统源码、有哪些应用领域?采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码
|
5月前
|
数据采集 监控 BI
C#实验室检验LIS信息系统源码 微生物检验、质控维护
LIS系统的主要目标是为检验室开展检验工作提供更加有效的系统支持。该系统将尽量减少以人工操作的方式来实现信息转移,减少在接收检验项目、报告结果和保存记录等工作中可能会出现的人为误差,为检验结果查询提供更有效的方法,节省了管理信息所需的琐碎时间和精力。为实验室技术人员提供智能化的运行模式,使处理诸如按照规程审核检验结果、取消检验项目、分析、处理存在重大疑问的检验结果、执行特殊的命令和处理质量控制等问题更轻松自如,这将使检验人员更快地获得准确清晰的检验结果。为临床医护人员提供在线设施,使他们可以及时准确地获得相关实验室信息。确保检验结果的可靠性和准确性,利用实验室管理信息系统的仪器监控和质量控制,
56 0
|
5月前
|
C#
C#中使用IntPtr.Size属性来判断当前系统是32位还是64位
这段代码首先检查 `IntPtr.Size`的值,如果是4,则输出"当前系统是32位";如果是8,则输出"当前系统是64位";如果都不是,就输出"未知系统位数"。
80 0
|
7月前
|
安全 API C#
C#.Net筑基-类型系统②常见类型--枚举Enum
枚举(enum)是C#中的一种值类型,用于创建一组命名的整数常量。它们基于整数类型(如int、byte等),默认为int。枚举成员可指定值,未指定则从0开始自动递增。默认值为0。枚举可以与整数类型互相转换,并可通过`[Flags]`特性表示位域,支持位操作,用于多选场景。`System.Enum`类提供了如`HasFlag`、`GetName`等方法进行枚举操作。
|
7月前
|
编译器 C#
C#.Net筑基-类型系统②常见类型 --record是什么类型?
`record`在C#中是一种创建简单、只读数据结构的方式,常用于轻量级数据传输。它本质上是类(默认)或结构体的快捷形式,包含自动生成的属性、`Equals`、`ToString`、解构赋值等方法。记录类型可以继承其他record或接口,但不继承普通类。支持使用`with`语句创建副本。例如,`public record User(string Name, int Age)`会被编译为包含属性、相等比较和`ToString()`等方法的类。记录类型提供了解构赋值和自定义实现,如密封的`sealed`记录,防止子类重写。
|
7月前
|
存储 安全 Unix
C#.Net筑基-类型系统②常见类型--日期和时间的故事
在System命名空间中,有几种表示日期时间的不可变结构体(Struct):DateTime、DateTimeOffset、TimeSpan、DateOnly和TimeOnly。DateTime包含当前本地或UTC时间,以及最小和最大值;DateTimeOffset增加了时区偏移信息,适合跨时区操作。UTC是世界标准时间,而格林尼治标准时间(GMT)不稳定,已被更精确的UTC取代。DateTimeOffset和DateTime提供了转换为UTC和本地时间的方法,以及各种解析和格式化函数。
|
6月前
|
C#
C#||设计一个学生的成绩管理系统
C#||设计一个学生的成绩管理系统
下一篇
DataWorks