web開發最麻煩的是做報表,特別是交叉報表。要將查詢得到的看起來平淡無奇的數據展開成復雜的報表不知要費煞多少周張。下次維護時看到冗長的SQL語句或長達數頁的程序代碼,都有種快要暈厥的感覺。
最近,這種好事又讓我碰上。公司因為費用統計的需要,要開發一份各分公司之間調貨量的統計表,如下圖所示。表中堅向為分公司帳套名稱(調出方),橫向為帳套中的客戶名稱(調入方),這是一份典型的交叉報表。
制作這份報表有兩個難點:
一、每一個分公司帳套對應數據庫服務器上一個數據庫,並分別存放在兩台以上的數據庫服務器中,因此報表數據必須跨服務器查詢得到。
二、將查詢得到的數據展開。
如果直接用SQL語句來完成這查詢和數據展開顯得太復雜,而且效率會很低。如果只通過SQL語句來查詢數據則會簡單很多,撇開安全問題不考慮,完全可以用SQL Server提供的Open系列函數來完成。展開數據如果通過代碼來完成,實在是一件很痛苦的事情,每當這時候我就會想起C/S開發中的報表控件,遺憾的是公司並沒有購買任何的WEB報表控件,只好自己來打制一個了.
說話間,我就拿出了我的看家寶貝:Delphi+Fast Report2.5(很久沒用了一直沒更新),東西雖舊但好用。首先,創建一個ActiveX Library和Active Server Object。
然後在TLB是添加ShowCrossReport方法,參數如下,其中:ConStr為數據庫的連接字符串,Command為SQL查詢語句,ReportFile是FastReport報表模板,通過它生成最終報表。
接著編寫ShowCrossReport的代碼。
function TkktWebCrossReport.ShowCrossReport(const ConStr, Command,
ReportFile: WideString): Integer;
var
ADOQ: TADOQuery;
//frdb: TfrDBDataSet;
rpt: TfrReport;
st: TMemoryStream;
ex: TfrHTML2Export;
buf: String;
e: TComponent;
begin
e := TComponent.Create(nil);
try
ADOQ := TADOQuery.Create(e);
try try
//生成查詢控件並查詢數據
ADOQ.Name := 'ADOQ';
ADOQ.ConnectionString := ConStr;
ADOQ.SQL.Add(Command);
ADOQ.Open;
{frdb := TfrDBDataSet.Create(nil); //Fast Report 2.5的交叉報表控件可以直接讀取ADODataSet數據
frdb.Name := 'frdb';
frdb.DataSet := ADOQ;}
st := TMemoryStream.Create; //報表生成後會導出到內在流中
//載入報表模板
rpt := TfrReport.Create(e);
rpt.Name := 'frReport1';
//rpt.Dataset := frdb;
rpt.LoadFromFile(ReportFile);
rpt.ShowProgress := false;
rpt.PrepareReport;
st.Position := 0;
//創建導出控件,FastReport有TfrHTMExport和TfrHTML2Export兩個導出到HTML格式的控件,TfrHTMExport以表格方式導出,默認沒有表格線,且存在數據錯位的問題,TfrHTML2Export以CSS格式導出,能完好的顯示表格線,我采用後者。
ex := TfrHTML2Export.Create(e);
ex.Navigator.Align := haLeft;
ex.ShowDialog := false;
//這是重點,將結果導出到內存流並送到客戶端
//導出到內存流的好處是不用在服務器生成中間文件,不用考慮權限和安全問題
//FastReport沒有ExportToStream函數,是我加的,見後面說明
rpt.ExportToStream(ex, st);
st.Position := 0;
SetLength(buf, st.Size);
st.Read(buf[1], st.Size);
Response.Write(buf);
st.Clear;
except on E: Exception do
Response.Write('<font color=red><b><h2>'+E.Message+'</h2></b></font>');
end;
finally
if ex <> nil then FreeAndNil(ex);
if st <> nil then FreeAndNil(st);
if rpt <> nil then FreeAndNil(rpt);
//if frdb <> nil then frdb.Free;
FreeAndNil(ADOQ);
end;
finally
e.Free;
end;
end;
最後,翻譯項目得到Dll文件,拷貝到服務器上並注冊。服務器組件就完成了。
接著,來段演示代碼。
先創建一個報表模板,並保存到服務器上,命名為rpt1.frf
然後是調用代碼(ASP.NET):
'通過ParseSQL生成SQL查詢語句
Private Function ParseSQL ...
Private Sub ShowReport()
Dim Cmd As String = ParseSQL()
If Cmd <> "" Then
Dim CrossReport = Server.CreateObject("CrossReport.kktWebCrossReport")
CrossReport.ShowCrossReport(clsSQL.DefaultConStr, Cmd, Page.MapPath(".") + "rpt1.frf")
CrossReport = Nothing
End If
End Sub