解析大型.NET ERP系統核心組件 查詢設計器 報表設計器 窗體設計器 工做流設計器 任務計劃設計器

企業管理軟件包含一些公共的組件,這些基礎的組件在每一個新項目立項階段就必須考慮。核心的穩定不變功能,方便系統開發與維護,也爲系統二次開發提供了諸多便利。好比通用權限管理系統,通用附件管理,通用查詢等組件,如果在項目開發前就準備好了這些組件,爲項目如期交付提供了保證。html

  • 查詢設計器 Query Designer  支持選擇一個或多個數據庫表,經過左右鏈接的方式構建查詢結果,支持直接手寫SQL語句設計查詢,支持調用存儲過程查詢,支持用代碼設計查詢。
  • 報表設計器 Report Designer 支持配置的方式生成報表參數對話框,支持報表多語言,支持報表參數設置與打印
  • 窗體設計器 Form Designer 支持用戶自定義界面控件的佈局(Layout)和外觀(Appearance),支持用戶修改界面控件的查找條件
  • 工做流設計器 Workflow Designer 支持自定義流程,支持自定義消息通知,支持自定義事件,支持計劃任務
  • 任務計劃設計器 Schedule Designer 支持計劃任務

 

查詢設計器 Query Designer

在系統維護過程當中,不停的增長新的字段,通常不會當即重寫系統現有的查詢,但須要一種方式能夠當即看到系統的更改,或者是系統現有的查詢不能知足客戶的需求,查詢設計器就是爲了解決系統查詢功能的不足開發的。數據庫

我考慮到了如下幾種查詢的方式,簡單介紹它們的實現方式供參考。編程

1 SQL語句  SQL Statements

假設客戶的系統維護人員懂SQL語句,通過多年客戶積累的系統,軟件公司也有許多經常使用的查詢語句,將這些查詢語句放到一個查詢功能中執行一下,便可獲取數據。服務器

參考下面的SqlDbx程序的例子,在系統中能夠對這個界面進行簡化,只保留輸入SQL語句和顯示查詢結果的地方。框架

image

爲了方便最終用戶分析結果數據,系統須要提供對查詢結果數據的導出(Microsoft Excel),過濾,排序,分組等功能。工具

ERP系統將每一個查詢保存起來,下次用戶只須要敲查詢編號便可看到查詢結果,並對結果數據進行操做。佈局

 

2  圖形化查詢設計 GUI Query Design

若是用戶不懂SQL語句,系統考慮提供一種圖形化的方式供用戶設計查詢。記得10年前本身在學習SQL語句的時候,是很是期待有一個智能化的工具,可讓我選擇要查詢的數據表和字段,再設置數據表之間關聯,最後就獲得我需用的查詢語句。系統參考了Access 的查詢設計器,參考一下經典的Access的查詢設計界面:學習

image

微軟Office套件中Access的查詢設計器通過多年的發展,應該是有足夠的理由相信這個界面是最容易讓非IT人士接受的查詢設計界面。只須要用鼠標選一下要查詢的表,再選擇要顯示的字段,系統自動產生相應的SQL語句。開發工具

圖紙化查詢設計這種功能會常常出如今報表設計器中,報表設計器通常都會附加一個圖形化的查詢設計工具,咱們能夠在那裏找到它的界面原型,通過簡化後變成ERP系統的查詢設計工具。ui

 

3  存儲過程查詢 Query By Stored Procedure

若是SQL語句或是表關聯也不能知足數據的查詢要求,系統能夠考慮增長存儲過程支持,以知足更復雜的查詢需求。在ERP的財務報表中,各類財務統計報表的確至關的繁瑣,非用存儲過程不可。咱們須要考慮好,如何將參數傳遞到存儲過程當中,顯示存儲過程的返回結果就可知足這種需求。爲此,設計以下的查詢語句:

EXEC  spRpt_OrderAmtTotal  %1, %2   

存儲過程的後面兩個參數是佔位符號,表示須要給此存儲過程傳遞參數。因而,還須要設計一種參數映射,設定參數的類型,長度,運行此查詢時,將用戶輸入的值替換到上面的兩個佔位置符中,傳遞到存儲過程當中。

image

存儲過程的定義已經定義它的參數類型,系統運行查詢時,系統須要將上面的%1的值所表明的值轉換成存儲過程須要的參數類型,好比上圖中的%1 所表明的Date From,系統須要用戶的輸入值進行強制類型轉換爲日期時間類型,再傳遞到存儲過程當中獲取返回結果。

 

4 程序查詢 Query By Program

用程序代碼寫查詢能夠分爲二種狀況,單據查詢,列表查詢。單據查詢只須要繼承原有的單據編輯窗體,設置窗體不可編輯,這樣就完成了單據查詢。列表查詢是指須要用戶輸入一種或幾種過濾條件,根據過濾條件獲得查詢結果。

單據查詢的代碼很是簡單,只是重設幾個屬性便可,參考下面的代碼例子。

public partial class SalesContractEnquiry : Foundation.Sales.Entry.SalesContractEntry
{
        public CostSheetEnquiry()
        {
              InitializeComponent();
   
              this.SupportAdd = false;              
              this.SupportDelete = false;
              this.SupportEdit = false;
        }

程序代碼中禁用了單據的新增,刪除,編輯操做,這樣單據窗體變也了查詢窗體。

列表查詢的界面參考以下,界面中上方是過濾條件輸入控件,下面顯示查詢結果。對查詢結果能夠導出Excel,過濾,分組,或是以圖表的方式呈現查詢結果。

image

 

報表設計器 Report Designer

報表設計器分二個組件,一個是報表設計,另外一個是報表顯示,前者是design,後者是render。市面上有許多報表設計工具,我比較熟悉是的水晶報表(Crystal Report)和報表服務(SQL Server Reporting Services)。剛畢業那時還接觸到開源的報表設計器RDLC Designer,是微軟報表服務的一個開源實現。工做中接觸水晶報表的時間比較多,個人技術總監寫的一個水晶報表查看器,全是反射調用作成的報表查看器,不依賴於水晶報表的版本,發現水晶報表對.NET的支持至關穩定,從Crystal Report for Visual Studio 2008 runtime到如今的Crystal Report 13.10,幾乎沒有改動代碼就能夠完美的運行技術總監的代碼,水晶報表是.NET報表製做的工業標準。

能力有限,實在沒有精力去維護一份報表設計器代碼,報表設計選用SAP的水晶報表設計器,這個工具備不少年沒有更新,目前能找到的最新版本是Crystal Report 200 SP2。

因此這一部分的內容測重於報表呈現(Render),力求設計一個完美的報表閱讀器。做爲核心功能,列出以下需求:

  • 報表設計完成後,不須要.NET編程便可調用報表,主要的困難在於參數傳遞。
  • 報表支持多語言,也就是支持英語,簡體,繁體三種語言顯示報表。
  • 報表部署方便,只須要安裝一個Crystal Report runtime就能夠運行報表。
  • 報表定製容易,用戶既能夠用系統提供的標準報表,也可用定製修改報表。

1  參數傳遞 Dynamic Parameter

水晶報表分三種類型的參數,經過調節這三個數值來改變水晶報表的數據。定義如下枚舉:

public enum ReportFieldType
{
        [DisplayText("Selection Formula"), StringValue("0")]
        SelectionFormula = 0,

        [DisplayText("Formula Field"), StringValue("1") ]
        FormulaField = 1,

        [DisplayText("Parameter"), StringValue("2")]
        Parameter = 2       
}
 

以上三種種方式的定義,與下面的水晶報表截圖能夠更清楚的瞭解它們的含義:

image

0 表示表記錄選擇條件,1 表示公式,2表示參數。 經過這三種方式,能夠定義以下表結構:

image

運行報表時,先根據上面的參數表生成控件,獲取控件的值,傳遞到水晶報表中,即完成了參數傳遞。

這樣開發的好處是技術支持人員可獨立製做和開發報表,不須要開發部專門爲每一個界面開發參數選擇界面。

 

2 多語言 Localization

如何只設計一份報表,卻可讓它同時以三種本地化語言顯示報表。經歷瞭如下幾種方案演化。

1)  定義一個LanguageCode的公式或參數,運行時由系統傳入到報表中來,表示當前要顯示的語言。水晶報表中每一個文字標籤都用公式表示,公式Ccy的例子參考以下:

if  LanguageCode=1 then "Currency"  
else if LanguageCode=2 then 「貨幣」
else   "貨幣"

從公式中能夠看到,1表示英語,2表示繁體中文,其它的數字表示簡體中文。這樣根據傳入的LanguageCode的值,來顯示文本標籤的值,實現了報表多語言顯示。

2) 使用.NET Localization方案,定義三種資源文件,分別是Resource.en-us.resx,Resource.zh-cn.resx,Resource.zh-tw.resx,編譯這個程序集後會生成三個帶語言標識的子程序集,.NET運行時會根據語言查找相應的資源文件,調用語言配對的資源。關鍵的代碼調用以下所示:

System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(cultureName);

3)  翻譯水晶報表控件TextObject

這個方法是藉助於水晶報表.NET API,找到水晶報表中須要翻譯的對象,通常是TextObject,將它翻譯成對應的本地化語言再顯示,這種方案深受報表開發人員喜好,開發報表時只須要按照標準英文版的作法,當須要顯示爲其它語言時,自動轉化爲本地化語言。可參考下面的代碼片斷以加深瞭解。

IEnumerator reportObjectEnumerator = (IEnumerator)ReflectionHelper.InvokeMethod(reportObjects, "GetEnumerator");

                while (reportObjectEnumerator.MoveNext())
                {
                    try
                    {
                        object reportObject = reportObjectEnumerator.Current;
                        object objectKind = ReflectionHelper.GetPropertyValue(reportObject, "Kind");
                        string objectKindName = Enum.GetName(objectKind.GetType(), objectKind);

                        if (string.CompareOrdinal(objectKindName, "TextObject") == 0 || string.CompareOrdinal(objectKindName, "FieldHeadingObject") == 0)
                        {
                            string text = (string)ReflectionHelper.GetPropertyValue(reportObject, "Text");
                            if (!string.IsNullOrEmpty(text))
                            {
                                string translatedText = ComponentCommon.TranslateText(text, false);
                                if (string.CompareOrdinal(text, translatedText) != 0)
                                    ReflectionHelper.SetPropertyValue(reportObject, "Text", translatedText);
                            }
                        }
                    }
                    catch
                    {
                    }
                }

反射調用一個對象要實現foreach的效果,須要調用GetEnumerator方法。

 

3 部署 Deployment

寫一個水晶報表運行庫的檢測程序,它能夠檢測安裝的水晶報表的版本。水晶報表控件所有用反射方法調用,參考下面的代碼例子,這樣就實現了編譯時不依賴於水晶報表版本的效果,部署時更加靈活方便。

 ReflectionHelper.SetPropertyValue(this._crystalReportViewer, "ReportSource", this._report);
 ReflectionHelper.InvokeMethod(this._crystalReportViewer, "Update");
 ReflectionHelper.InvokeMethod(this._crystalReportViewer, "Zoom", new System.Type[] { typeof(int) }, new object[] { 1 });
 ReflectionHelper.InvokeMethod(this._crystalReportViewer, "Zoom", new System.Type[] { typeof(int) }, new object[] { this._zoomFactor });
              

4  報表定製

引入一個替代報表(Alternate report)的概念,將標準報表嵌入到程序集或放置在標準報表目錄中,系統也支持一個替代報表的路徑選項,系統讀取報表時優先查找替代報表路徑中的報表文件,找到則用替代報表顯示,不然繼續在標準報表路徑中查找報表。由於兩種報表放置在不一樣的路徑中,因此相同的報表文件也不會相互影響和覆蓋,解決了客戶定製報表與系統報表取捨的難題。替代(Alternate)的概念還用在物料清單的替代物料中,生產發料時當物料不夠發料,能夠去查找替代物料發料,比如咱們口渴了能夠喝汽水,也能夠選擇喝涼茶。

 

窗體設計器 Form Designer

窗體設計器在ERP/MIS領域應用的至關普遍,Visual Studio自己就是一個設計精良的窗體設計器。金蝶ERP的BOS系統所有依賴於它的窗體設計器,在此基礎上作數據綁定和插件開發。微軟的InfoPath也是一個自定義表單工做,經常使用來作OA系統的自定義表單。剛畢業那會也很是喜歡研究form designer re-host技術,惋惜一直沒有找到技術突破點,也不知道這樣的設計是否合理。曾經接觸過《像搭積木同樣作軟件》這本書,全書講解的就是以窗體設計器爲基礎,作表達式求值,作事件綁定和屬性綁定,不須要編碼而開發企業管理軟件。

然而這種美好的技術終究是一種幻覺,Visual Studio 仍舊是最流行的開發工具,窗體開發仍舊是企業管理軟件開發的重點。窗體設計器所產生的代碼,只有一小部分間接的用途。我沒有深刻接觸這個主題,但就我所知道的知識點列舉以下。

1 窗體設計器能夠生成CS/VB/Xml 三種代碼格式。NET動態編譯技術已經很成熟,直接調用.NET API就能夠將CS/VB代碼編譯爲程序集,在這裏我選擇第三種格式,我並不須要用窗體設計器徹底開發一個新功能,那樣涉及到數據綁定,主從數據等一系列難題,我只須要設計器產生Xml格式,運行時我能夠優先加載這個自定義佈局,因此Xml格式足矣。

2 要設計的窗體對象是現有的系統功能。用戶可能要修改界面控件的佈局或是外觀。實施過程當中,看到不少客戶喜歡將控件標成紅色或藍色以加快閱讀速度,然而當滿屏幕都是花花綠綠的控件,反而會下降閱讀理解的速度。另外一個就是控件的佈局,一些用戶不須要的選項卡,控件能夠經過窗體設計器隱藏。

3 窗體設計器最重要的地方是能夠修改界面控件的查找。參考下圖。

image

窗體設計器能夠修改Customer No中查找按鈕的過濾條件,這一重要的功能大大減輕了開發人員的負擔。

4  窗體設計器不能夠用來徹底從新開發一個新功能,從界面設計到數據綁定,再到數據驗證,以及數據之間的勾稽

引用,這些功能的實現不可能經過鼠標拖放控件就完成。即便經過大量的二次開發,像金蝶那樣作成BOS,它的可擴展性和靈活性仍那以控制。好比單價 * 數量=金額,要作到輸入單價或數量時,自動計算金額。BOS要考慮的內容項太多,我終究是完全放棄這種開發模式,只用到窗體設計器的一小部分功能:控件外觀與佈局修改,控件查找定製。

 

工做流設計器  Workflow Designer

工做流實現的四大基礎功能:通知提醒,批覈,計劃任務,調用自定義代碼。

爲了實現這個目標,基於微軟的.NET WF,作了如下工做以達到上述目的。

1  定義活動庫 Activities

活動是工做流中的代碼執行單元,一個工做流定義自己也是一個活動。根據業務須要,定義了以下活動庫:

文檔批覈活動,發送消息活動,發送郵件活動,調用.NET 代碼活動,執行數據庫查詢活動,報表生成活動。

2 定義工做流類型 Workflow

根據業務的須要,定義如下幾種業務類型:

單據類:單據保存,單據更新,單據刪除,單據新建。

業務類:文檔送審,文檔批覈,業務過賬,業務完成,業務取消。

任務計劃:在預約時間執行工做流

3 工做流設計器 Workflow Designer Rehost

MSDN 中提供了rehost工做流設計器的例子,直接把它拿來參考,添加自定義活動組件和自定義工做流類型,再將工做流設計器輸出格式保存爲XOML,便可完成工做流設計器的絕大部分功能。

image

這裏比較複雜的一點是自定義條件表達式,須要一個與對象表達式求值工具。Code Project中有許多條件表達式的例子,難點在於如何將業務單據的狀態與工做流掛接。

4 工做流監控器 Workflow Monitor

系統須要一個可視化的工具查看每一個流程當前正在運行的結點,MSDN中有例子可參考。

5 工做流持久化 Workfllow Persistence

.NET提供的基礎服務,建立一個工做流狀態保存數據庫和一個工做流跟蹤數據庫。

6  工做流服務器 Workflow Server

工做流與系統業務部分的交互,專門開一個獨立的端口用於數據的讀寫。

 

任務計劃設計器 Schedule Designer

企業應用中的做業調度,常見的操做以下:郵件提醒和告警,執行文件傳輸操做,建立複雜報表。

系統分爲兩種計劃任務調度器,一種是基於SQL Server Agent實現,定時執行SQL語句,另外一種是基於Quartz框架庫實現,用.NET代碼開發任務調度程序。

1 基於SQL Server Agent

SQL Server Job是一個按期執行腳本的對象,給它加一個時間選項便可完成基於SQL Server Agent的計劃調度程序。

image

這個界面會依據控件值的不一樣,生成不一樣參數的SQL Server Job,參考下面的程序代碼片斷:

 private void btnOk_Click(object sender, EventArgs e)
        {
            if (Schedule == null)
                Schedule = new SQLschedule();

            Schedule.name = txtName.Text.Trim();
            if (chkEnabled.Checked)
                Schedule.enabled = 1;
            else
                Schedule.enabled = 0;

            if (cmbScheduleType.SelectedIndex == 1)
            {
                Schedule.freq_type = 1; // One-time
                Schedule.active_start_date = dtOneTimeOccurDate.Value;
                Schedule.active_end_date = new DateTime(9999, 12, 31);
                Schedule.active_start_time = dtOneTimeOccurTime.Value;
                Schedule.active_end_time = new DateTime(2000, 1, 1, 23, 59, 59);
            }
            else
            {
                if (cmbFrequencyOccurs.SelectedIndex == 0)
                {
                    Schedule.freq_type = 4; // Daily
                    Schedule.freq_interval = (int) numFrequencyRecurs.Value;
                }

                if (cmbFrequencyOccurs.SelectedIndex == 1)
                {
                    Schedule.freq_type = 8; // Weekly
                    int freq_interval = 0;
                    if (chkFrequencyRecursSun.Checked)
                        freq_interval += 1;
                    if (chkFrequencyRecursMon.Checked)
                        freq_interval += 2;
                    if (chkFrequencyRecursTue.Checked)
                        freq_interval += 4;
  

這個工具來源於Code Project上的一篇文章,能夠用SQL Agent Job Editor嘗試找到它。

2  Quartz.NET 任務調度框架

這是由Java項目轉化過來的著名項目,用.NET代碼重定了它的邏輯。看一個最簡單的任務計劃的源代碼。

public class HelloJob : IJob
{
        
        private static ILog _log = LogManager.GetLogger(typeof(HelloJob));
        
        public HelloJob()
        {
        }
        
        public virtual void  Execute(IJobExecutionContext context)
        {
            // Say Hello to the World and display the date/time
            _log.Info(string.Format("Hello World! - {0}", System.DateTime.Now.ToString("r")));
        }

}
 
 

Quartz.NET處理好了關於任務計劃調度方面的各個細節,很容易上手,官方提供的例子也至關豐富。

相關文章
相關標籤/搜索