最近为客户组织了一项C/S架构程序的开发培训,讲解C/S应用程序开发中需要注意的点。

我主要是做C/S方面的ERP/CRM程序开发,界面是用Windows Forms技术,有遗漏或错误的地方欢迎批评指正。

1 异常处理

为处理应用程序中的异常,需要增加以下代码。

Application.ThreadException += new ThreadExceptionEventHandler(eh.OnThreadException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

2  Excel文件生成

我们以Infragistics Excel作为生成Excel的基础组件。它提供一套面向对象的模型以简化Exel文件操作。

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);
        }
     }
 }

3 第三方类库

为了简化第三方类库的部署,我在项目中直接将需要引用到的第三方类库作为嵌入的资源生成为一个程序集。

这样在部署时,根据需要将我生成的程序集复制到执行文件目录即可。同时需要增加一个程序集加载事件。

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

4 日志追踪

部署到生产环境中后,难免会出一些不可预料的异常。我使用SmartInspectPro来跟综这些问题。

官方网址是 http://www.gurock.com/smartinspect/

只需要下面简单的几行代码,就可以将程序中的异常信息或对象信息搜集起来,传送到日志查看工具中。

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);

5 自动更新

以文件所在的位置来区分,我们考虑局域网,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方式的更新方法,由于配置稍微麻烦我们并未采用。

6 版本检测

由于有多个客户的版本同时存在,我们在系统启动时,会检测当前文件夹中的所有文件的版本是否一致,如果不一致则抛出异常,终止执行。可参考如下的代码片段。

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");                    }

7 源代码控制

我要提到的不是Team Foundation,SVN或Visual SourceSafe等源代码管理工具,而是如何控制客户正在使用的版本和程序员的开发版本。程序员的开发版本功能最多,同时也问题最多,许多新功能加入到程序中,没有经过完整的测试。

Team Foundation有一个分支管理功能,可以将客户正在使用的版本(正式版)看作是开发版本的(程序员开发)的一个子分支,每当在开发版中check in某项bug fix或feature并且经过完整测试后,将开发版本的变更集(changeset)合并到客户正在使用的分支版本中。

8 x86 x64 Any CPU的选择

现在.NET程序员真是太幸福了,编译时设定为Any CPU,JIT运行时根据机器的架构(x86,x64)生成相应的机器码。

我们的项目绝大多数情况下都选Any CPU作为生成架构。如果遇到一些编译依赖项它只有x86版本的程序集,这时我们考虑将依赖于这个x86的程序集的功能单独设计为一个DLL或EXE,这样整个项目还是以Any CPU架构来编译。

有时候出于安全原因,有一些代码以native语言来编写,比如C++,这时我们就分别生成两套(x86和x64)程序集,在部署时根据目标平台来部署相应架构的文件。

9 资源(图片,文档模板,标准报表)

为简化部署,我们将常用的资源项编译到一个程序集中。可参考以下代码提取嵌入的资源项。

 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));
       }
   }
}

运行时我们从程序集中提取资源到硬盘临时文件夹,根据需要生成相应的文件返回给用户。

10 数据库访问

大型的项目离不开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的性能比较。

11 性能

写的不合理的代码会导致性能问题,但不至于上升到怀疑技术的程度。微软的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

12 事件销毁

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);
}
这个方法也是为了改善性能。
 

C/S架构应用程序开发培训笔记的更多相关文章

  1. WeChat小程序开发-初学者笔记(一)

    WeChat小程序开发学习第一天: 完成学习目标: 1.安装并了解Wechat小程序的基本环境, 2.可以利用已学知识的结合简单实现helloWorld界面. 学习过程: 1.首先在微信平台上进行相关 ...

  2. JavaScript高级程序开发3笔记

      Js对象 注意:js基本数据类型不是对象,但是"abc".match()这种,可以调用对象的方法,是因为调用方法是临时产生了一个wrapper的包装对象,this指向它: Js ...

  3. 第6章 AOP与全局异常处理6.5-6.11 慕课网微信小程序开发学习笔记

    https://coding.imooc.com/learn/list/97.html 目录: 第6章 AOP与全局异常处理6-1 正确理解异常处理流程 13:236-2 固有的处理异常的思维模式与流 ...

  4. 第7章 数据库访问与ORM 慕课网微信小程序开发学习笔记

    第7章 数据库访问与ORM https://coding.imooc.com/learn/list/97.html 目录: 7-1 数据库操作三种方式之原生SQL 19:09 7-2 从一个错误了解E ...

  5. 第1-5章 慕课网微信小程序开发学习笔记

    第1章 前言:不同的时代,不同的Web --微信小程序商城构建全栈应用 http://note.youdao.com/noteshare?id=a0e9b058853dbccf886c1a890594 ...

  6. 第6章 AOP与全局异常处理6.1-6.4 慕课网微信小程序开发学习笔记

    第6章 AOP与全局异常处理 https://coding.imooc.com/learn/list/97.html 目录: 第6章 AOP与全局异常处理6-1 正确理解异常处理流程 13:236-2 ...

  7. 《Linux就该这么学》培训笔记_ch20使用LNMP架构部署动态网站环境

    <Linux就该这么学>培训笔记_ch20使用LNMP架构部署动态网站环境 文章最后会post上书本的笔记照片. 文章主要内容: 源码包程序 LNMP动态网站架构 配置Mysql服务 配置 ...

  8. Linux及Arm-Linux程序开发笔记(零基础入门篇)

    Linux及Arm-Linux程序开发笔记(零基础入门篇)  作者:一点一滴的Beer http://beer.cnblogs.com/ 本文地址:http://www.cnblogs.com/bee ...

  9. 《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程

    前段时间看APUE,确实比较详细,不过过于详细了,当成工具书倒是比较合适,还是读一读这种培训机构的书籍,进度会比较快,遇到问题时再回去翻翻APUE,这样的效率可能更高一些. <嵌入式linux应 ...

随机推荐

  1. js-静态、原型、实例属性

    本篇来说一下js中的属性: 1.静态属性 2.原型属性 3.实例属性 静态属性: function klass(){} var obj=new klass(); klass.count=0; klas ...

  2. 背后的故事之 - 快乐的Lambda表达式(一)

    快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...

  3. Python标准模块--Unicode

    1 模块简介 Python 3中最大的变化之一就是删除了Unicode类型.在Python 2中,有str类型和unicode类型,例如, Python 2.7.6 (default, Oct 26 ...

  4. SQL Server-聚焦UNIOL ALL/UNION查询(二十三)

    前言 本节我们来看看有关查询中UNION和UNION ALL的问题,简短的内容,深入的理解,Always to review the basics. 初探UNION和UNION ALL 首先我们过一遍 ...

  5. 苹果强制使用HTTPS传输了怎么办?——关于HTTPS,APP开发者必须知道的事

    WeTest 导读 2017年1月1日起,苹果公司将强制使用HTTPS协议传输.本文通过对HTTPS基础原理和通信过程内容的讲解,介绍APP开发者在这个背景下的应对办法. 几周前,我们在<htt ...

  6. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  7. 邮件中嵌入html中要注意的样式

    工作中常会有需求向用户发送邮件,需要前端工程师来制作html格式的邮件,但是由于邮件客户端对样式的支持有限,要兼容很多种浏览器需要注意很多原则: 1.邮件使用table+css布局 2.邮件主要部分在 ...

  8. 深入解析Sqlite的完美替代者,android数据库新王者——Realm

    写在前面: 又到一年一度七夕虐狗节,看着大家忍受着各种朋友圈和QQ空间还有现实生活中的轮番轰炸,我实在不忍心再在这里给大家补刀,所以我觉得今天不虐狗,继续给大家分享有用的. 如果你比较关心androi ...

  9. ASP.NET Core 中文文档目录

    翻译计划 五月中旬 .NET Core RC2 如期发布,我们遂决定翻译 ASP.NET Core 文档.我们在 何镇汐先生. 悲梦先生. 张仁建先生和 雷欧纳德先生的群中发布了翻译计划招募信息,并召 ...

  10. The Zen of Python

    Beautiful is better than ugly. 优美总比丑陋好Explicit is better than implicit. 直率总比含蓄好Simple is better than ...