工作多年后才明白的.NET底層開發(fā)技術(shù)
參加工作已經(jīng)有五年了。由于是非計算機科系,一開始的工作是做網(wǎng)頁,維護ASP腳本程序,有時候也要做圖片,總而言之,一項任務(wù)交到手里,沒有借口說不會做,想辦法彌補然后把任務(wù)做完。后來才得到機會,參與編程開發(fā)工作。而且,當(dāng)時一心想用C#.NET做開發(fā),不希望參與DELPHI的開發(fā),偏偏工作經(jīng)過層層的轉(zhuǎn)發(fā),交到手里的就是DELPHI開發(fā)。經(jīng)歷努力,再后來就真的用.NET進行開發(fā)工作,一直持續(xù)到現(xiàn)在。消極的觀念會說,學(xué)的很雜,做的也雜,積極一點的想法,把老板交給你的任務(wù)完成,就是稱職的員工?,F(xiàn)在正值畢業(yè)生找工作的時間,一時找不到開發(fā)的工作,也不要急,先聽從公司的安排做個軟件測試,或是文檔管理之類的職位,等有空缺的開發(fā)人員的機會,你再毛遂自薦,同樣可以進入程序員這個領(lǐng)域。機會是留給有準(zhǔn)備的人的,難道不是嗎?
一直用.NET做ERP/MIS類型的軟件,學(xué)業(yè)不精,可以完成工作任務(wù),業(yè)余時間也制作一些集成化的工具包,下面分享一下我認為的.NET領(lǐng)域里的底層技術(shù)。
1 通信技術(shù)(Remoting,WCF, ASMX)
通常的三層架構(gòu),數(shù)據(jù)庫,數(shù)據(jù)訪問層,界面層。通訊技術(shù)定義了三層組件之間的調(diào)用約定,以及方法。這樣說起來有些抽象,舉例為證?,F(xiàn)在要制作一個進銷存項目,基本的功能是進倉,出倉,轉(zhuǎn)倉,查詢功能要能查詢到倉庫進出日記帳,
庫存余額。對于一筆物料為FLEX0901的進倉業(yè)務(wù),用ORM的方式完成,偽代碼如下
- InventoryMovmentEntity receipt=new InventoryMovmentEntity(“REF1108080001”,"RECEIPT”);
- InventoryMovmentDetailEntity detail=receipt.InventoryMovmentDetails.AddNew();
- detail.ItemNo=”FLEX0901”;
- detail.MoveDate=DateTime.Now;
- InventoryMovementDAL.Instance.Save(receipt);
這里有幾個問題處理的不錯,比如
1) ORM的數(shù)據(jù)讀寫,關(guān)注面從拼湊SQL讀寫到構(gòu)建實體,這是進步。如果用DAL+SQL Script的方式完成,偽碼如下
- string mySql=”INSERT ICMOVH(RefNo,Direction) VALUES(‘REF1108080001’, 'RECEIPT’) ";
- InventoryMovementDAL.Instance.ExecuteNonQuery(mySql);
- mySql=”INSERT ICMOVD(ItemNo,MoveDate) VALUES(‘FLEX0901’,'2011/8/8’) ";
- InventoryMovementDAL.Instance.ExecuteNonQuery(mySql);
這里省略了拼湊參數(shù)值的句段,直接把值放到SQL語句中去。
后一種方式,明顯的缺陷時,當(dāng)添加新的字段,對系統(tǒng)擴展時,修改和維護起來的成本,明顯大于前者。
2) 使用了單件模式,InventoryMovementDAL.Instance來統(tǒng)一操作數(shù)據(jù)讀寫,而不是這樣
- InventoryMovementDAL inventoryDAL=new InventoryMovementDAL();
- inventoryDAL.Save(receipt);
從這個單件模式,引出了通訊技術(shù)的必要性。當(dāng)有很多個用戶,同時操作進倉功能,意味著同時有很多個InventoryMovementDAL的數(shù)據(jù)寫入操作,這時產(chǎn)生的問題
◆ 不好控制前后兩張單據(jù)的進倉單參考編號重復(fù)。為保證不重復(fù),在保存之前,我們需要到數(shù)據(jù)庫檢查一次是否已經(jīng)存在該參考編號的進倉單,也就是InventoryMovementDAL的Save方法的開頭,
要包含這樣一段代碼,以DAL+SQL Script的方式表示
- tring mySql=” SELECT COUNT(1) FROM ICMOVH WHERE RefNo=’REF1108080001’ ";
- bool existing=InventoryMovementDAL.Instance.ExecuteScalar(mySql)>0;
可以想像,當(dāng)并發(fā)用戶為100時,每一筆進倉業(yè)務(wù),需要預(yù)先一次數(shù)據(jù)檢查,來回于數(shù)據(jù)庫,這樣的程序性能肯定不好。
◆ 對于庫存報表,多個并發(fā)的InventoryMovementDAL會導(dǎo)致數(shù)據(jù)前后不統(tǒng)一。庫存余額報表現(xiàn)在可以讀到物料FLEX0901還有100個pc,一會當(dāng)有進倉單入庫200pc的FLEX0901時,如果不手動刷新數(shù)據(jù),此時的報表仍然顯示的物料FLEX0901的庫存余額是100,而不是300. 這有時候是不可接受的結(jié)果。
可以做一個timer,定期刷新庫存余額。這里的問題是,InventoryMovementDAL只有一個實例,無法自己告訴自己,已經(jīng)有新的庫存了,重新讀取數(shù)據(jù)。這里需要一種并發(fā)機制,告訴InventoryMovementDAL實例,有新的數(shù)據(jù)加入到庫存余額中,需要刷新報表。
◆ 有一些數(shù)據(jù)項,是全局的,對于整個系統(tǒng)都是唯一的,需要特殊處理。
比如ERP系統(tǒng)允許的并發(fā)用戶,同時允許有10個用戶連接進入系統(tǒng),超過則無法處理;
ERP系統(tǒng)的一個用戶把當(dāng)前系統(tǒng)的默認貨幣從HK$改進了US$,其它的用戶,要能知道這個改變,此時,不太可能讓當(dāng)前正在操作業(yè)務(wù)的用戶退出,重新進入系統(tǒng);
當(dāng)發(fā)生網(wǎng)絡(luò)故障時,ERP系統(tǒng)要能知道網(wǎng)絡(luò)故障,并suspend當(dāng)前正在進行的操作;這一點可以理解為一條有效的改善,當(dāng)無法連接到SQL Server時,要阻止當(dāng)前正在錄入數(shù)據(jù)的用戶,否則他很辛苦的錄入了數(shù)據(jù),而系統(tǒng)又無法保存,給出一個提示A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections 用戶會抱怨你的軟件沒有做好,盡管這不是你的錯。
如下圖,當(dāng)前用戶正在編輯客戶資料,發(fā)生網(wǎng)絡(luò)故障,界面被Disable了。借助于通訊技術(shù)中的并發(fā),可以實現(xiàn)。

通篇都沒有給Remoting,WCF打廣告,說Remoting,WCF的好處。當(dāng)你的程序遇到這幾個問題時,是否可以考慮下通訊技術(shù),來改善性能和客戶體驗。
2 反射,動態(tài)編譯
反射是動態(tài)獲取程序集的元數(shù)據(jù)的一種技術(shù),這句話是做.NET程序員面試題目的一個的答案,你可選擇記住它,就好比高中生物學(xué)里面講到的細胞的結(jié)構(gòu)的課程時,細胞由細胞膜,細胞質(zhì)和細胞核組成。根據(jù)做程序的經(jīng)驗,Never ask why不是好習(xí)慣,即使是微軟的API,有時候違反了調(diào)用約定,也會很抓狂。
請看下面這一段代碼
- Assembly assembly = Assembly.GetExecutingAssembly();
- object entryForm = Activator.CreateInstance(formBaseType) as Form;
- entryForm .MdiParent = this;
- entryForm .Show();
- entryForm .Activate();
代碼的含義比較簡單,從當(dāng)前程序集中創(chuàng)建formBaseType類型,并調(diào)用它的方法。從方法名上來看,大概可以看出,這是一段MDI創(chuàng)建child子窗體,并顯示子窗體的代碼。
這段小代碼,也是插件式框架的基本思路,請參考《Management Console 工具管理類軟件通用開發(fā)框架(開放源碼)》中的例子來體會它的用處。
在ERP/MIS系統(tǒng)中,應(yīng)用反射的例子,實在是太多了。數(shù)據(jù)訪問接口InventoryMovementDAL,借助于反射,來查找并調(diào)用它的實現(xiàn)類; 窗體也它的子窗體借助于反射來獲取屬性,傳遞值;ERP系統(tǒng)的整個框架,也是借助于反射搭建起來.
看下面的圖,ERP的三個模塊Paradox.ERP.SystemAdministration, Paradox.ERP.Engineering,Paradox.ERP.Inventory被Paradox.Framework.Kernal反射調(diào)用,如果再寫一個Paradox.ERP.Sales的銷售模塊,幾乎不需要改動,就可以讓它被框架調(diào)用。

動態(tài)編譯的例子,是應(yīng)用到一個工資系統(tǒng)中. 請看圖

對于如何解析工資的formular公式,有若干種辦法,這里使有的是動態(tài)編譯的方法。把每一個工資項看成是一個類型class的屬性,F(xiàn)ormular的內(nèi)容則放到一個方法中去,當(dāng)成表達式計算求職,最后應(yīng)用反射,返回各屬性的值即可。
- public class FormularCalculation
- {
- public static object Build(string[] items, string formular)
- {
- string nameSpace = "A";
- string className = "FormularCalculation";
- string methodName = "Run";
- CSharpCodeProvider compiler = new CSharpCodeProvider();
- CompilerParameters paras = new CompilerParameters();
- paras.GenerateExecutable = false;
- paras.GenerateInMemory = true;
- StringBuilder classSrc = new StringBuilder();
- classSrc.Append(" using System;"+Environment.NewLine);
- classSrc.Append(" namespace "+nameSpace+" { " + Environment.NewLine);
- classSrc.Append("public class " + className + "{ " + Environment.NewLine);
- foreach (string item in items)
- {
- classSrc.Append("public decimal " + item + ";" + Environment.NewLine);
- }
- classSrc.Append("public void Run() { 基本工資=5000; " + Environment.NewLine);
- string [] format= Regex.Split(formular,Environment.NewLine);
- foreach (string prop in format)
- {
- classSrc.Append(prop +" ;"+ Environment.NewLine);
- }
- classSrc.Append("}"+Environment.NewLine);
- classSrc.Append("}" + Environment.NewLine);
- classSrc.Append("}" + Environment.NewLine);
- string source = classSrc.ToString();
- CompilerResults result = compiler.CompileAssemblyFromSource(paras, source);
- CompilerErrorCollection error= result.Errors;
- Assembly assembly = result.CompiledAssembly;
- object eval = assembly.CreateInstance(nameSpace+"."+className);
- MethodInfo method = eval.GetType().GetMethod(methodName);
- object reobj = method.Invoke(eval, null);
- return eval;
- }
- }
調(diào)用方法如下
string[] items = { "應(yīng)發(fā)合計","基本工資","獎金","福利費", "扣款合計","社保","稅","實發(fā)合計","應(yīng)稅所得額"};
string formular=@ ” 應(yīng)發(fā)合計=基本工資+ 獎金 + 福利費 - 扣款合計;
扣款合計=社保 + 稅+ 應(yīng)稅所得額;
實發(fā)合計=應(yīng)發(fā)合計- 扣款合計; ";
- object obj = FormularCalculation.Build(items, formular);
- Type type = obj.GetType();
- foreach (PropertyInfo fi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public
- | BindingFlags.Instance | BindingFlags.DeclaredOnly))
- {
- string value = fi.Name; //取到計算后的各個屬性的值
- }
動態(tài)編譯還應(yīng)用于軟件的加密,在內(nèi)存中產(chǎn)生加密程序的源代碼,動態(tài)編譯并運行,檢測是否符合license授權(quán)。
3 ORM(NHibernate,LLBL Gen) 對象關(guān)系映射
雖然可以找出很多理由來拒絕ORM,比如performance不好,接口不好用,沒有NHibernate設(shè)計器,是的,這都是理由。但是,一旦接觸過ORM之后,我發(fā)現(xiàn)做系統(tǒng)再也離不開這個工具。相對于ERP/MIS類的系統(tǒng),大部分時間都是和SQL的讀寫在搏斗,ORM帶給你的幾個好處,是不可忽視的。
1) 增加或刪除數(shù)據(jù)庫字段,界面和程序幾乎不需要改動。這一點我非常認同,即使是很穩(wěn)定的系統(tǒng),也避免不了要做customization,也要加些字段,如果用SQL拼湊,你幾乎要改動所有的相關(guān)內(nèi)容,而且還無法獲取編譯器的語法檢查的好處,ORM在編譯期間就可以檢測出一些類型不匹配的問題。
2 )界面和邏輯的真正分離 改動計算邏輯,不需要改動界面,也就是實現(xiàn)MVC,MVP的模式,其實我們可以不用管這兩個模式,我們只是在用ORM來讀寫數(shù)據(jù)庫。
3 )代碼更幽雅,調(diào)試起來更容易,維護方便。
Linq技術(shù)之后,微軟大力發(fā)展Entity Framework,不推薦在項目中使用。MS的優(yōu)點是,它發(fā)現(xiàn)一項技術(shù)很有用,或為開發(fā)一個很得力的工具,最終它會做的很好,比如Visual Studio,Office,但是這是需要時間的,它要不停的學(xué)習(xí),觀察,改善,特別像API這類的東西,如果更新太快,會對項目產(chǎn)生較大的風(fēng)險,實際項目中最需要的是穩(wěn)定的API。MS打算要放棄的東西,它會慢慢減少資源,慢慢減少關(guān)注的次數(shù),時間一長,最后就淡出了開發(fā)人員的視線。
NHibernate經(jīng)過多年的發(fā)展,穩(wěn)定,好用,有龐大的Java社區(qū)(Hibernate)的支持,不愁遇到問題沒有答案。
4 工作流 Workflow
目前微軟推出了兩個版本的工作流,.NET 3.5和.NET 4.0的,應(yīng)該把它看成兩個產(chǎn)品,并不是簡單的版本升級。
ERP/MIS領(lǐng)域常見的需求有
1)采購單審批(如果條件) 要求:
當(dāng)金額大于等于500且采購員為A時,需要通過May的審批
當(dāng)金額大于等于500且采購員為B時,需要通過Jack的審批
當(dāng)金額小于500時,不需要通過審批,可直接過帳
2 ) 當(dāng)發(fā)生工程更改ECN時,要通知生產(chǎn)部重排計劃,通知貨倉安排發(fā)料。
像這種類型的需求,每個企業(yè)的要求都不一樣,要達到定制的目的,又不想為每個不同的客戶分別寫代碼,非用到工作流不可。你可以不選擇用工作流,那就為不同的客戶定制代碼,也行。
MS的工作流也做成了中間件,你需要盡可能的override

請通過查看《信息化基礎(chǔ)建設(shè) 工作流開發(fā)》,了解更多關(guān)于工作流的心得體會。
原文:http://www.cnblogs.com/JamesLi2015/archive/2011/08/09/2131729.html
【編輯推薦】