最近爲客戶組織了一項C/S架構程序的開發培訓,講解C/S應用程序開發中須要注意的點。html
我主要是作C/S方面的ERP/CRM程序開發,界面是用Windows Forms技術,有遺漏或錯誤的地方歡迎批評指正。git
爲處理應用程序中的異常,須要增長如下代碼。程序員
Application.ThreadException += new ThreadExceptionEventHandler(eh.OnThreadException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
咱們以Infragistics Excel做爲生成Excel的基礎組件。它提供一套面向對象的模型以簡化Exel文件操做。github
excelWorkbook = new Workbook();
Worksheet currentWorksheet = this.excelWorkbook.Worksheets.Add("WorkSheet1");
foreach (var cell in currentWorksheet.GetRegion("A1:D1"))
{
cell.CellFormat.Fill = CellFill.CreateSolidFill(Color.Gray);
cell.CellFormat.Font.ColorInfo = new WorkbookColorInfo(Color.White);
}
currentWorksheet.Rows[0].Cells[0].Value = "Order ID";
currentWorksheet.Rows[0].Cells[1].Value = "Contact Name";
currentWorksheet.Rows[0].Cells[2].Value = "Shipping Address";
currentWorksheet.Rows[0].Cells[3].Value = "Order Date";
currentWorksheet.Columns[0].Width = 3000;
currentWorksheet.Columns[0].CellFormat.Alignment = HorizontalCellAlignment.Left;
currentWorksheet.Columns[1].Width = 7100;
currentWorksheet.Columns[2].Width = 3000;
currentWorksheet.Columns[2].CellFormat.Alignment = HorizontalCellAlignment.Left;
currentWorksheet.Columns[3].Width = 6100;
若是須要將網格數據導出爲Excel,它專門爲此提供一個導入格式對象,簡單的調用如下代碼便可達到目的。數據庫
using (System.Windows.Forms.SaveFileDialog dialog = new System.Windows.Forms.SaveFileDialog())
{
dialog.DefaultExt = "xls";
dialog.Filter = Shared.ExportToFileFilter;
dialog.Title = Microsoft.Common.Shared.TranslateText("Export to File");
dialog.FileName = this.Text;
if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}
if (dialog.FilterIndex == 1 || dialog.FilterIndex == 2)
{
using (UltraGridExcelExporter exporter = new UltraGridExcelExporter())
{
exporter.BandSpacing = BandSpacing.None;
exporter.Export(gridFunction, dialog.FileName);
}
}
}
爲了簡化第三方類庫的部署,我在項目中直接將須要引用到的第三方類庫做爲嵌入的資源生成爲一個程序集。安全
這樣在部署時,根據須要將我生成的程序集複製到執行文件目錄便可。同時須要增長一個程序集加載事件。session
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return EmbeddedAssembly.Get(args.Name);
}
這個技巧來自於CodeProject,參考如下地址Load DLL From Embedded Resource架構
部署到生產環境中後,不免會出一些不可預料的異常。我使用SmartInspectPro來跟綜這些問題。tcp
官方網址是 http://www.gurock.com/smartinspect/ide
只須要下面簡單的幾行代碼,就能夠將程序中的異常信息或對象信息蒐集起來,傳送到日誌查看工具中。
SiAuto.Si.Connections = "file(filename=c:\\log.sil)";
SiAuto.Si.Enabled = true;
SiAuto.Main.LogMessage("First Message!");
日誌的內容能夠寫到文件,或是經過TCP或命名管道(named-pipes)發送到工具窗口中。
SiAuto.Si.Connections = string.Format("tcp(host={0},timeout=10000)", Microsoft.Common.Shared.ApplicationServer);
以文件所在的位置來區分,咱們考慮局域網,HTTP,FTP三種自動更新方式。.NET有許多自動更新組件,簡單的列舉。
http://wyday.com/wyupdate/
序號 | 名稱 | 地址 |
1 | AutoUpdater.NET | https://autoupdaterdotnet.codeplex.com/ |
2 | wyUpdate | http://wyday.com/wyupdate/ |
3 | Updater | http://www.codeproject.com/Articles/9566/Updater |
4 | NetSparkle | http://netsparkle.codeplex.com/ |
5 | NAppUpdate | https://github.com/synhershko/NAppUpdate |
6 | AutoUpdater | https://autoupdater.codeplex.com/ |
微軟自己也提供ClickOnce方式的更新方法,因爲配置稍微麻煩咱們並未採用。
因爲有多個客戶的版本同時存在,咱們在系統啓動時,會檢測當前文件夾中的全部文件的版本是否一致,若是不一致則拋出異常,終止執行。可參考以下的代碼片斷。
private static void VerifyAssembliesVersion()
{
string[] files = Directory.GetFiles(Application.StartupPath, "Microsoft.EnterpriseSolution.*.dll", SearchOption.TopDirectoryOnly);
Parallel.ForEach<string>(files, file =>
{
FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(file);
if (string.CompareOrdinal(fileVersion.FileVersion, AssemblyVersion.FileVersion) != 0)
throw new AppException(string.Format("File version mismatch detected"); }
}
}
我要提到的不是Team Foundation,SVN或Visual SourceSafe等源代碼管理工具,而是如何控制客戶正在使用的版本和程序員的開發版本。程序員的開發版本功能最多,同時也問題最多,許多新功能加入到程序中,沒有通過完整的測試。
Team Foundation有一個分支管理功能,能夠將客戶正在使用的版本(正式版)看做是開發版本的(程序員開發)的一個子分支,每當在開發版中check in某項bug fix或feature而且通過完整測試後,將開發版本的變動集(changeset)合併到客戶正在使用的分支版本中。
如今.NET程序員真是太幸福了,編譯時設定爲Any CPU,JIT運行時根據機器的架構(x86,x64)生成相應的機器碼。
咱們的項目絕大多數狀況下都選Any CPU做爲生成架構。若是遇到一些編譯依賴項它只有x86版本的程序集,這時咱們考慮將依賴於這個x86的程序集的功能單獨設計爲一個DLL或EXE,這樣整個項目仍是以Any CPU架構來編譯。
有時候出於安全緣由,有一些代碼以native語言來編寫,好比C++,這時咱們就分別生成兩套(x86和x64)程序集,在部署時根據目標平臺來部署相應架構的文件。
爲簡化部署,咱們將經常使用的資源項編譯到一個程序集中。可參考如下代碼提取嵌入的資源項。
private static void ExtractEmbeddedResource(string resourceLocation, string output)
{
using (System.IO.Stream stream = Assembly.Load("Microsoft.Data").GetManifestResourceStream(resourceLocation))
{
using (BinaryReader r = new BinaryReader(stream))
using (FileStream fs = new FileStream(output, FileMode.OpenOrCreate))
using (BinaryWriter w = new BinaryWriter(fs))
{
w.Write(r.ReadBytes((int)stream.Length));
}
}
}
運行時咱們從程序集中提取資源到硬盤臨時文件夾,根據須要生成相應的文件返回給用戶。
大型的項目離不開ORM,對象之間的運算與關聯已不容易相處,若是還要去考慮數據讀寫,那程序的可維護性相對差不少。ORM帶來的好處除了數據讀寫的徹底解放,還有強類型的數據綁定。爲此,咱們的數據讀寫接口都是用Code Smith模板生成的,好比一個對象的讀取方法
AccountEntity account = null;
using (DataAccessAdapter adapter = GetCompanyDataAccessAdapter(sessionId, companyCode))
{
account = new AccountEntity(accountNo);
bool found = adapter.FetchEntity(account, prefetchPath, null, fieldList);
if (!found) throw new RecordNotFoundException(accountNo, "Invalid Account No.");
}
ORM帶來另外一個好處是強類型綁定,這樣在設計時便可預知對象的類型和它的屬性成員,方便作數據綁定。
ORM的第三個好處,多是勝於直接寫SQL語句(事務腳本模式)的地方,它會默認檢測對象有哪些屬性發生值改變,這樣在保存對象時只會生成這些有發生值變動的SQL更新語句。許多同事甚至於個人上司都極度懷疑ORM的性能,我不肯定他們是否真的驗證過SQL語句(事務腳本模式)和ORM的性能比較。
寫的不合理的代碼會致使性能問題,但不至於上升到懷疑技術的程度。微軟的Entity Framework有那麼多客戶在用,難道這些客戶的程序都是小規模,小應用嗎? .NET代碼的性能問題,我舉例如下幾個。
1) 主動要求GC進行垃圾回收會致使性能問題。
GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized);
最後在stackoverflow中找到回答是,任什麼時候候都不該該調用此代碼,註釋以上代碼後程序速度是快不少了。
2) 釋放內存的代碼會致使性能問題
[DllImport("kernel32.dll")]
private static extern bool SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
具體緣由可參考這裏
http://www.cnblogs.com/kex1n/archive/2011/01/26/2286427.html
3) 反射會影響性能
這個結論不是空口而談,我是用ANTS Performance Profiler 8親自測試反射和非反射的代碼的運行時間得出的結論。
好比我想增長一個動態報表控件,根據系統安裝的水晶報表的版原本加載水晶報表控件。因而有如下兩種寫法
//反射版
object _crystalReportViewer;
_crystalReportViewer = ReflectionHelper.CreateObjectInstance(CrystalReportHelper.GetLongAssemblyName("CrystalDecisions.Windows.Forms", CrystalReportVersion), "CrystalDecisions.Windows.Forms.CrystalReportViewer");
//非反射版
CrystalDecisions.Windows.Forms.CrystalReportViewer _crystalReportViewer;
_crystalReportViewer=new CrystalDecisions.Windows.Forms.CrystalReportViewer();
以後調用Load方法,反射版的Load方法須要耗費的時間要比非反射版本多一倍左右。
ReflectionHelper.InvokeMethod(_crystalReportViewer, "Load", new System.Type[] {typeof (string), obj3.GetType()}, new object[] {path, obj3});
至因而否要用反射,個人結論是取決於應用場景。若是應用要求運行速度第一,可維護性其次。則應用最快的那種方法。好比有些醫藥行業的錄單模塊,對鍵盤的響應速度要求極高,這時用反射是不合適的。
反射能夠經過預處理(pre-init,pre-load)等方式提升響應速度,這樣可在性能和可維護性方面共贏。
4) 頻繁的數據庫讀寫會有性能問題
ORM實在是太方便了,各類計算和取值,只須要取到對象便可完成,代碼的可複用性高。不過有時候會致使性能問題。
在包含不少邏輯操做時,爲了取一個字段值而去頻繁的構造對象是不合適的。好比在一個採購單列表功能中,爲了取到採購單的部門編碼對應的部門名稱,咱們頻繁的去取數據庫,而且以構造對象的方法來完成,這樣會致使性能問題。正確的作法是構造DataTable來完成,構造一個包含1000行記錄的DataTable要比構造1000個部門對象(DepartmentEntity)要快不少。
ORM另外一個好處是按需分配,咱們能夠根據須要只讀取部分字段的值,比如SELECT * 與SELECT 具體字段的區別。
參考如下的代碼,爲了提升性能,咱們的系統絕大多數狀況下都是以這種方式讀取數據庫字段。
IItemManager itemMan = ClientProxyFactory.CreateProxyInstance<IItemManager>();
ExcludeIncludeFieldsList fieldList = new ExcludeIncludeFieldsList(false);
fieldList.Add(ItemFields.Description);
fieldList.Add(ItemFields.StockUom);
fieldList.Add(ItemFields.ScrapRate);
fieldList.Add(ItemFields.DefBomNo);
fieldList.Add(ItemFields.ExtendedDesc);
fieldList.Add(ItemFields.RohsCompliance);
fieldList.Add(ItemFields.TempDescription);
fieldList.Add(ItemFields.Specification);
fieldList.Add(ItemFields.ColorCode);
ItemEntity item = itemMan.GetValidItem(Shared.CurrentUserSessionId, this.PartItemNo, null, fieldList, Shared.SystemParameter.TailorSinojoint);
ExcludeIncludeFieldsList 對象能夠理解爲SELECT語句中的具體字段的集合。
5) 控件的不合適操做會引發性能問題
設定選項卡控件的選中的方法,如下代碼中第一種要比第二種快
//快一點
tabControl.SelectedTab=tabControl.Tabs[0];
//慢一些
tabControl.Tabs[0].Selected=true;
水晶報表控件的設定數據源鏈接的時候,ApplyLogonInfo要比SetConnection慢。
//快一點的代碼
reportDocument.DataSourceConnections[0].SetConnection(
connectionStringBuilder.DataSource,
connectionStringBuilder.InitialCatalog,
connectionStringBuilder.UserID,
connectionStringBuilder.Password
);
//慢一些的代碼
crDatabase = crReportDocument,Database
crTables = crDatabase.Tables
For Each crTable In crTables
crTableLogOnInfo = crTable.LogOnInfo
crTableLogOnInfo.ConnectionInfo = crConnectionInfo
crTable.ApplyLogOnInfo(crTableLogOnInfo)
Next
C/S程序包含豐富的事件機制,我認爲可用性要高於B/S程序。可是隨之而來的是代碼要比B/S慢。
當咱們的程序中有太多事件時,咱們須要在窗本釋放時,將這些事件從委託鏈中移出。
protected override void ReleaseResources()
{
this.btnPrintRouting.Click -= new System.EventHandler(this.btnPrintRouting_Click);
this.btnPrintMaterialsList.Click -= new System.EventHandler(this.btnPrintMaterialsList_Click);
this.btnSortMaterials.Click -= new System.EventHandler(this.btnSortMaterials_Click);
}
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
components.Dispose();
}
ReleaseResources();
base.Dispose(disposing);
}
這個方法也是爲了改善性能。