前段时间开发报表,采用了 ReportViewer + RDLC , 开发整理如下.
分页思路
MS 的DataGrid ,GridView,和 ReportViewer 分页机制差不多,都需要绑定全部数据,MS控件自动处理分页。 只是ReportViewer 是按页面布局分页的,而不是按数据条数。绑定全部数据的方式会无谓的增加服务器压力,尤其是数据计算和表关联多的情况下。为了减轻服务器压力,我们采用数据分页,数据传到应用服务器端后,再加工一下(插入空数据),做也可分页数据源。
如果数据库有 100 条数据,每页10条数据,那存储过程返回10条数据后,还需要再插入90条数据。当数据大的时候,插入的数据也很多。有没有插入数据再少一点的办法呢?
当然是有了,没有就不写了。。先提两个概念:维度和指标.
维度: 是指公司,项目,区域等要分析的对象.
指标: 是指针对维度进行计算的结果
1. 先对维度进行分页.
2.针对分页结果,有针对性的进行指标计算 ,可以把每个计算列,做成函数,方便调用,同时,也方便维护和测试。
实现方案
方案是把页码做成分组,这样再插入的时候,就只插入 Celling( 90 ÷ 10 ) 条的空分组数据即可。 插入的数据少了 n 倍。
存存过程示例代码:
CREATE proc [dbo].[R_ANALYSIS]
(
@Query varchar(50)= '', --查询条件
@PageIndex int = 1 , --从1开始
@PageSize int = 50 , --每页条数,传入 <=0 表示不分页。
@PageCount int=0 output --总页数 输出参数
)
as
begin
declare @RowCount int ;
--写业务,返回分页结果 , 分页结果放到 表变量 @list 中
/*示例:
With Query_Rank as
(
select ROW_NUMBER() OVER (order by 1 )as RowNumber ,*
from T_ROOM
)
select Col1,col2, dbo.Func1(Col1) , dbo.Func2(col1,col2)
fromQuery_Rank
whereRowNumber between 100 and 150
*/
if(@PageSize > 0 )
begin
--计算 @RowCount
set @PageCount = ceiling( @RowCount *1.0 / @PageSize) ;
end
else
begin
set @PageCount = 1
end
end
所需要的两个程序加工函数:
public static IEnumerable<T> ReportWrap<T>(this IEnumerable<T> Source, int CurrentPageIndex, int PageCount)
where T : IReportModel, new()
{
if (PageCount < 2)
{
foreach (var item in Source)
{
yield return item;
}
yield break;
}
Func<int, T> CreateEmptyObj = (CurrentIndex) =>
{
T retVal = new T();
retVal.SysPageIndex = CurrentIndex;
return retVal;
};
for (int i = 0; i < CurrentPageIndex; i++)
{
yield return CreateEmptyObj(i + 1);
}
foreach (var item in Source)
{
item.SysPageIndex = CurrentPageIndex + 1;
yield return item;
}
for (int i = CurrentPageIndex + 1; i < PageCount; i++)
{
yield return CreateEmptyObj(i + 1);
}
}
public static DataTable ReportWrap(this DataTable Source, int CurrentPageIndex, int PageCount)
{
if (PageCount <=1)
{
return Source;
}
Func<int, DataRow> CreateEmptyObj = (CurrentIndex) =>
{
DataRow retVal = Source.NewRow();
retVal["SysPageIndex"] = CurrentIndex;
return retVal;
};
for (int i = 0; i < CurrentPageIndex; i++)
{
Source.Rows.InsertAt(CreateEmptyObj(i + 1), 0);
}
for (int i = CurrentPageIndex + 1; i < PageCount; i++)
{
Source.Rows.Add(CreateEmptyObj(i + 1));
}
return Source;
}
程序调用函数代码,很简单:
DataTable dt = db.Exec_SP_DataTable("sp_Report_ModuleVisitStatisView", pars);
pageCount = Convert.ToInt32(pars[6].Value);
dt.ReportWrap(pageIndex -1 , pageCount);
之后,再把 DataTable 转成 实体列表。方便RDLC数据绑定。
另注:
上面的 IReportModel , 是一个只包含 SysPageIndex 属性的接口. 它约束了报表返回的Model 必须继承自 IReportModel 且必须拥有 无参构造函数.
1. 无参构造函数的作用是 初始化空对象. 比如: 字符串类型的,要设置为 string.Empty . 数值类型要设置为 0.
2.接口是约束Model 必须具有 SysPageIndex 属性用于页码值.
报表RDLC 的设置:
绑定数据源,添加父组,选 SysPageIndex。在该列的属性上设置: Hidden = True , Width = 0cm。即隐藏该列。后端绑定代码:
ReportDataSource data = new ReportDataSource("LogSource", MyBiz.GetLogReportData("query", pageIndex, pageSize, out pageCount));
this.ReportViewer1.LocalReport.LoadReportDefinition(new FileStream(Server.MapPath("~/Admin/Report/Log.rdlc")));
this.ReportViewer1.LocalReport.DataSources.Clear();
this.ReportViewer1.LocalReport.DataSources.Add(data);
至此,分页就做好了.