企業管理軟件包含一些公共的組件,這些基礎的組件在每一個新項目立項階段就必須考慮。核心的穩定不變功能,方便系統開發與維護,也爲系統二次開發提供了諸多便利。好比通用權限管理系統,通用附件管理,通用查詢等組件,如果在項目開發前就準備好了這些組件,爲項目如期交付提供了保證。html
在系統維護過程當中,不停的增長新的字段,通常不會當即重寫系統現有的查詢,但須要一種方式能夠當即看到系統的更改,或者是系統現有的查詢不能知足客戶的需求,查詢設計器就是爲了解決系統查詢功能的不足開發的。數據庫
我考慮到了如下幾種查詢的方式,簡單介紹它們的實現方式供參考。編程
假設客戶的系統維護人員懂SQL語句,通過多年客戶積累的系統,軟件公司也有許多經常使用的查詢語句,將這些查詢語句放到一個查詢功能中執行一下,便可獲取數據。服務器
參考下面的SqlDbx程序的例子,在系統中能夠對這個界面進行簡化,只保留輸入SQL語句和顯示查詢結果的地方。框架
爲了方便最終用戶分析結果數據,系統須要提供對查詢結果數據的導出(Microsoft Excel),過濾,排序,分組等功能。工具
ERP系統將每一個查詢保存起來,下次用戶只須要敲查詢編號便可看到查詢結果,並對結果數據進行操做。佈局
若是用戶不懂SQL語句,系統考慮提供一種圖形化的方式供用戶設計查詢。記得10年前本身在學習SQL語句的時候,是很是期待有一個智能化的工具,可讓我選擇要查詢的數據表和字段,再設置數據表之間關聯,最後就獲得我需用的查詢語句。系統參考了Access 的查詢設計器,參考一下經典的Access的查詢設計界面:學習
微軟Office套件中Access的查詢設計器通過多年的發展,應該是有足夠的理由相信這個界面是最容易讓非IT人士接受的查詢設計界面。只須要用鼠標選一下要查詢的表,再選擇要顯示的字段,系統自動產生相應的SQL語句。開發工具
圖紙化查詢設計這種功能會常常出如今報表設計器中,報表設計器通常都會附加一個圖形化的查詢設計工具,咱們能夠在那裏找到它的界面原型,通過簡化後變成ERP系統的查詢設計工具。ui
若是SQL語句或是表關聯也不能知足數據的查詢要求,系統能夠考慮增長存儲過程支持,以知足更復雜的查詢需求。在ERP的財務報表中,各類財務統計報表的確至關的繁瑣,非用存儲過程不可。咱們須要考慮好,如何將參數傳遞到存儲過程當中,顯示存儲過程的返回結果就可知足這種需求。爲此,設計以下的查詢語句:
EXEC spRpt_OrderAmtTotal %1, %2
存儲過程的後面兩個參數是佔位符號,表示須要給此存儲過程傳遞參數。因而,還須要設計一種參數映射,設定參數的類型,長度,運行此查詢時,將用戶輸入的值替換到上面的兩個佔位置符中,傳遞到存儲過程當中。
存儲過程的定義已經定義它的參數類型,系統運行查詢時,系統須要將上面的%1的值所表明的值轉換成存儲過程須要的參數類型,好比上圖中的%1 所表明的Date From,系統須要用戶的輸入值進行強制類型轉換爲日期時間類型,再傳遞到存儲過程當中獲取返回結果。
用程序代碼寫查詢能夠分爲二種狀況,單據查詢,列表查詢。單據查詢只須要繼承原有的單據編輯窗體,設置窗體不可編輯,這樣就完成了單據查詢。列表查詢是指須要用戶輸入一種或幾種過濾條件,根據過濾條件獲得查詢結果。
單據查詢的代碼很是簡單,只是重設幾個屬性便可,參考下面的代碼例子。
public partial class SalesContractEnquiry : Foundation.Sales.Entry.SalesContractEntry { public CostSheetEnquiry() { InitializeComponent(); this.SupportAdd = false; this.SupportDelete = false; this.SupportEdit = false; }
程序代碼中禁用了單據的新增,刪除,編輯操做,這樣單據窗體變也了查詢窗體。
列表查詢的界面參考以下,界面中上方是過濾條件輸入控件,下面顯示查詢結果。對查詢結果能夠導出Excel,過濾,分組,或是以圖表的方式呈現查詢結果。
報表設計器分二個組件,一個是報表設計,另外一個是報表顯示,前者是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),力求設計一個完美的報表閱讀器。做爲核心功能,列出以下需求:
水晶報表分三種類型的參數,經過調節這三個數值來改變水晶報表的數據。定義如下枚舉:
public enum ReportFieldType { [DisplayText("Selection Formula"), StringValue("0")] SelectionFormula = 0, [DisplayText("Formula Field"), StringValue("1") ] FormulaField = 1, [DisplayText("Parameter"), StringValue("2")] Parameter = 2 }
以上三種種方式的定義,與下面的水晶報表截圖能夠更清楚的瞭解它們的含義:
0 表示表記錄選擇條件,1 表示公式,2表示參數。 經過這三種方式,能夠定義以下表結構:
運行報表時,先根據上面的參數表生成控件,獲取控件的值,傳遞到水晶報表中,即完成了參數傳遞。
這樣開發的好處是技術支持人員可獨立製做和開發報表,不須要開發部專門爲每一個界面開發參數選擇界面。
如何只設計一份報表,卻可讓它同時以三種本地化語言顯示報表。經歷瞭如下幾種方案演化。
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方法。
寫一個水晶報表運行庫的檢測程序,它能夠檢測安裝的水晶報表的版本。水晶報表控件所有用反射方法調用,參考下面的代碼例子,這樣就實現了編譯時不依賴於水晶報表版本的效果,部署時更加靈活方便。
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 });
引入一個替代報表(Alternate report)的概念,將標準報表嵌入到程序集或放置在標準報表目錄中,系統也支持一個替代報表的路徑選項,系統讀取報表時優先查找替代報表路徑中的報表文件,找到則用替代報表顯示,不然繼續在標準報表路徑中查找報表。由於兩種報表放置在不一樣的路徑中,因此相同的報表文件也不會相互影響和覆蓋,解決了客戶定製報表與系統報表取捨的難題。替代(Alternate)的概念還用在物料清單的替代物料中,生產發料時當物料不夠發料,能夠去查找替代物料發料,比如咱們口渴了能夠喝汽水,也能夠選擇喝涼茶。
窗體設計器在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 窗體設計器最重要的地方是能夠修改界面控件的查找。參考下圖。
窗體設計器能夠修改Customer No中查找按鈕的過濾條件,這一重要的功能大大減輕了開發人員的負擔。
4 窗體設計器不能夠用來徹底從新開發一個新功能,從界面設計到數據綁定,再到數據驗證,以及數據之間的勾稽
引用,這些功能的實現不可能經過鼠標拖放控件就完成。即便經過大量的二次開發,像金蝶那樣作成BOS,它的可擴展性和靈活性仍那以控制。好比單價 * 數量=金額,要作到輸入單價或數量時,自動計算金額。BOS要考慮的內容項太多,我終究是完全放棄這種開發模式,只用到窗體設計器的一小部分功能:控件外觀與佈局修改,控件查找定製。
工做流實現的四大基礎功能:通知提醒,批覈,計劃任務,調用自定義代碼。
爲了實現這個目標,基於微軟的.NET WF,作了如下工做以達到上述目的。
活動是工做流中的代碼執行單元,一個工做流定義自己也是一個活動。根據業務須要,定義了以下活動庫:
文檔批覈活動,發送消息活動,發送郵件活動,調用.NET 代碼活動,執行數據庫查詢活動,報表生成活動。
根據業務的須要,定義如下幾種業務類型:
單據類:單據保存,單據更新,單據刪除,單據新建。
業務類:文檔送審,文檔批覈,業務過賬,業務完成,業務取消。
任務計劃:在預約時間執行工做流
MSDN 中提供了rehost工做流設計器的例子,直接把它拿來參考,添加自定義活動組件和自定義工做流類型,再將工做流設計器輸出格式保存爲XOML,便可完成工做流設計器的絕大部分功能。
這裏比較複雜的一點是自定義條件表達式,須要一個與對象表達式求值工具。Code Project中有許多條件表達式的例子,難點在於如何將業務單據的狀態與工做流掛接。
系統須要一個可視化的工具查看每一個流程當前正在運行的結點,MSDN中有例子可參考。
.NET提供的基礎服務,建立一個工做流狀態保存數據庫和一個工做流跟蹤數據庫。
工做流與系統業務部分的交互,專門開一個獨立的端口用於數據的讀寫。
企業應用中的做業調度,常見的操做以下:郵件提醒和告警,執行文件傳輸操做,建立複雜報表。
系統分爲兩種計劃任務調度器,一種是基於SQL Server Agent實現,定時執行SQL語句,另外一種是基於Quartz框架庫實現,用.NET代碼開發任務調度程序。
SQL Server Job是一個按期執行腳本的對象,給它加一個時間選項便可完成基於SQL Server Agent的計劃調度程序。
這個界面會依據控件值的不一樣,生成不一樣參數的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嘗試找到它。
這是由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處理好了關於任務計劃調度方面的各個細節,很容易上手,官方提供的例子也至關豐富。