不用第三方之C#實現大容量報表系統(轉)

iDotNetSpace發表於2010-04-14

概述

    現在企業中,都會有或多或少的報表來展示業務結果。實現報表有很多種方式,最簡單的就是直接用紙質寫出來展示,這種方式不需要任何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;

}

///

/// 設定具體的操作,以便服務可以執行它的工作。

///

protected override void OnStart(string[] args)

{

// TODO: 在此處新增程式碼以啟動服務。

Start();

}

///

/// 停止此服務。

///

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的頁面來支援,這部分比較簡單,而且和後臺服務這部分內容相對獨立,不再做過多的說明。

    以上僅是對實現思路的一個描述,不包括所有的程式碼,有興趣的朋友可以參考自己開發,並且加上自己的思路,這樣才是最大的收穫。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-659735/,如需轉載,請註明出處,否則將追究法律責任。

相關文章