最近项目中要用到水晶报表,水晶报表的功能的确非常强大,但是有一个问题一直困扰着我,那就是它对于每种数据表结构都必须建一个报表文件。尽管它有两种使用方式,“pull”模式和“push”模式。但是推模式下仍然需要先根据dataset的结构生成相应的报表文件。这样就很不灵活,试想如果之前并不知道数据集dataset的结构,怎么用报表?即便知道有哪些表格,如果表格数太多,对于每个表格生成一个报表文件,那也是很繁琐的一件事。所以,有没有方法建立一个通用报表文件,然后可以在运行时根据dataset 的具体结构来确定报表的结构呢?查了很多资料,很少有详细的解决方案。
在翻阅了一些资料,借鉴了多种做法后,终于解决了这个问题,下面把思路阐述如下:
第一步:
新建一个web网站,然后在项目中新建一个空白报表,命名为CommonReport.rpt,打开报表,在字段资源管理器中右键选“数据库字段”,选择“数据库专家”,在弹出窗口中选“创建新连接”中的OLEDB,然后选择SQL Server的提供程序,点击下一步,如图p1所示,跟着向导做下去,中间要输入连接信息,并选择一个数据库,“完成”后,可以看到连上了你所选的数据库,我选了本地SQLServer服务器上的“Northwind”数据库,它是Sql server中自带的一个数据库。然后随便选择这个库里的一张表,这里我选择第一张表“Categories”添加到右面,如图p2所示。完成后,应该是图p3所示结果,可以看到字段资源管理器中的数据库字段里有了一张表Categories,和它的四个字段,此时第一步完成了,后面不需要再管它了。
第二步:
在“字段资源管理器”中建立公式字段,公式字段的个数根据数据库里所有表格中字段最多的那个表格的字段数来定。这里我建立了6个公式字段,注意,这些公式字段都是空的。然后将这6个公式字段依次拖放到报表的详细资料栏里。
右键点击某个公式字段对应的页眉中文本对象(如图4中的“FormulaField6”)弹出菜单,选择“设置对象格式”,得到图中的格式化编辑器对话框。选择对话框中的“抑制显示”复选框后面的图标按钮,然后在弹出的对话框中输入图p4右下方的一段代码(if… else…),如图p4所示。
完成后点“保存并关闭”。剩下的其它几个字段如法炮制。完成后就可以保存报表文件,报表制作完成。
第三步:
新建网页用来测试我们生成的报表,首先生成一个GetDataSet.aspx页面,该页面装载时连接数据库,生成DataSet,并将它存入Session对象中。还有一个按钮,在事件处理代码中调用另外一个页面showReport.aspx。
showReport.aspx这个页面里面有个报表浏览器CrystalReportViewer控件,它从session中取出DataSet,用CrystalReportViewer显示DataSet中的数据。
GetDataSet.aspx.cs的代码:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;
public partial class GetDataSet : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
SqlConnection sqlConnection = new SqlConnection ( "Server=localhost;User Id=sa;Initial Catalog=Northwind" ) ;
sqlConnection.Open();
SqlDataAdapter sqlDataAdapter1 = new SqlDataAdapter("SELECT CustomerID , CompanyName,City,Country FROM Customers", sqlConnection);
DataSet dsDataSet = new DataSet () ;
sqlDataAdapter1.Fill (dsDataSet,"Customers") ;
//使用SqlDataAdapter的Fill方法填充DataSet
sqlConnection.Close ( ) ;
//关闭数据连接
Session["DataSet"] = dsDataSet;
}
protected void Button1_Click(object sender, EventArgs e)
{
Page.RegisterClientScriptBlock("open", "<script language='javascript'>window.open('showReport.aspx','_blank')</script>");
}
}
showReport.aspx.cs的详细代码:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using CrystalDecisions.CrystalReports.Engine;
public partial class showReport : System.Web.UI.Page
{
private ReportDocument CommonReport;
///<summary>
///根据具体查询得到的ADO.NET数据集配置水晶报表
///</summary>
private void ConfigureCrystalReports()
{
//生成一个报表对象
CommonReport = new ReportDocument();
//报表文件路径
string reportPath = Server.MapPath("CommonReport.rpt");
//根据路径装载指定报表文件,以进行报表配置操作
CommonReport.Load(reportPath);
//从Session中取得数据集,该数据集是从其它页面查询数据库得到后保存在Session中的
DataSet dataSet = (DataSet)Session["DataSet"];
//将数据集中的表格的名称修改为报表文件中连接的数据表的名字,保证两个表格名称一致
dataSet.Tables[0].TableName=CommonReport.Database.Tables[0].Name;
//获取数据集中字段个数,也是报表中要显示的列的个数
int CountOfColumns = dataSet.Tables[0].Columns.Count;
//定义一个字符串数组,在for循环中生成每个公式字段对象的文本(格式是"{tablename.columnname}"),并保存到数组中以备使用
String[] Text4formularFields = new String[CountOfColumns];
for (int index = 0; index < CountOfColumns; index++)
{
Text4formularFields[index] = "{" + CommonReport.Database.Tables[0].Name + "." + dataSet.Tables[0].Columns[index].ColumnName + "}";
}
//获取报表文件的数据定义对象
DataDefinition dataDefinition = CommonReport.DataDefinition;
//获取数据对象中的公式字段集合
FormulaFieldDefinitions formularFields = dataDefinition.FormulaFields;
for (int columnIndex = 0; columnIndex < CountOfColumns;columnIndex++)
{
//将公式字段的空白文本替换为根据当前数据集dataSet具体字段生成的文本
formularFields[columnIndex].Text=Text4formularFields[columnIndex];
//页眉中的文本对象(也就是报表中每列的标题)设置为dataSet的具体字段名
TextObject CurrentText = (TextObject)(CommonReport.ReportDefinition.Sections["Section2"].ReportObjects[columnIndex]);
CurrentText.Text = dataSet.Tables[0].Columns[columnIndex].ColumnName;
}
//设置报表对象的数据源为dataSet中的同名表格
CommonReport.SetDataSource(dataSet.Tables[CommonReport.Database.Tables[0].Name]);
//绑定到报表浏览器
CrystalReportViewer1.ReportSource = CommonReport;
}
private void Page_Init(object sender, EventArgs e)
{
ConfigureCrystalReports();
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
上面两个页面运行后的效果,如图p5所示。
如果把GetDataSet.aspx.cs文件中的SQL查询字符串换成”SELECT EmployeeID , LastName,FirstName,Title,Country FROM Employees”,其它的地方都不改动,报表文件还是用CommonReport.rpt。得到的报表输出如图p6所示。
图P1
图P2
图P3
图P4
图P5
图P6
现在我们可以看到,不管数据库服务器上有多少数据表,我们只要用CommonReport.rpt这一个报表文件就可以显示了。
注意:
1, 做第一步的原因只是因为需要一个表的连接,否则在调用report对象的setDataSource()方法时会报错,提示“报表中没有表存在”。换句话说就是为报表文件生成表格时提供一个表名,报表最终显示的并不是来自这张表里的数据,
2, 图4中那段代码是用Crystal的语言写的,作用是判断当前列是否有数据,如果没有则不在页眉中显示该列的标题。它的作用就是因为我们所设的公式字段并不一定都用得上,因为我们是按字段最多的情况确定的公式字段个数,多出来的公式字段就要屏蔽掉。
3, showReport.aspx.cs中CommonReport.SetDataSource(dataSet.Tables[CommonReport.Database.Tables[0].Name]);这个语句,参数必须明确指定dataSet中的表名,如果只用dataSet作为参数,则报表中不显示数据,不知道是什么原因。
4, 这里是用公式字段解决的,也可以用未绑定字段来解决这个问题,道理是一样的。
5, 如图p6所示,第一列没有对齐。这是后来发现的问题,没找到什么原因。
参考资料:
http://blog.csdn.net/haibodotnet/archive/2003/11/09/21504.aspx,csdn上的一篇文章,参考了他的思路
http://msdn2.microsoft.com/zh-cn/library/ms225984(VS.80).aspx,msdn上水晶报表的类库
《Crystal Reports for Visual Studio .NET 高级编程》,David McAmis 著