最近为客户组织了一项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. TODO:GitHub创建组织的步骤

    TODO:GitHub创建组织的步骤 使用GitHub进行团队合作,写这个步骤主要作用是为了OneTODO作为一个团队组织进行代码的分享,让更多人来参与. 使用帐号.密码登录GitHub 2.右上角加 ...

  2. Partition2:对表分区

    在SQL Server中,普通表可以转化为分区表,而分区表不能转化为普通表,普通表转化成分区表的过程是不可逆的,将普通表转化为分区表的方法是: 在分区架构(Partition Scheme)上创建聚集 ...

  3. SQL Server-聚焦使用视图若干限制/建议、视图查询性能问题,你懵逼了?(二十五)

    前言 上一节我们简单讲述了表表达式的4种类型,这一系列我们来讲讲使用视图的限制,简短的内容,深入的理解,Always to review the basics. 避免在视图中使用ORDER BY 上一 ...

  4. 基于RN开发的一款视频配音APP(开源)

    在如今React.ng.vue三分天下的格局下,不得不让自己加快学习的脚步.虽然经常会陷入各种迷茫,学得越多会发现不会的东西也被无限放大,不过能用新的技术作出一些小项目小Demo还是会给自己些许自信与 ...

  5. js闭包 和 prototype

    function test(){ var p=200; function q(){ return p++; } return q; } var s = test(); alert(s()); aler ...

  6. BPM配置故事之案例5-必填与水印文本

    物资申请表改好了,但是没过两天老李又找来了. 老李:这个表格每次都是各个部门发给我们,再由我们采购部来填,太影响效率了,以后要让他们自己填. 小明:那就让他们填呗,他们有权限啊. 老李:可是他们说不会 ...

  7. myrocks复制中断问题排查

    背景 mysql可以支持多种不同的存储引擎,innodb由于其高效的读写性能,并且支持事务特性,使得它成为mysql存储引擎的代名词,使用非常广泛.随着SSD逐渐普及,硬件存储成本越来越高,面向写优化 ...

  8. 从史上八大MySQL事故中学到的经验

    本文列举了史上八大MySQL宕机事件原因.影响以及人们从中学到的经验,文中用地震级数来类比宕机事件的严重性和后果,排在最严重层级前两位的是由于亚马逊AWS宕机故障(相当于地震十级和九级). 一.Per ...

  9. transient关键字的用法

    本篇博客转自 一直在路上 Java transient关键字使用小记 1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,Java ...

  10. 完整部署CentOS7.2+OpenStack+kvm 云平台环境(1)--基础环境搭建

    公司在IDC机房有两台很高配置的服务器,计划在上面部署openstack云平台虚拟化环境,用于承载后期开发测试和其他的一些对内业务.以下对openstack的部署过程及其使用做一详细介绍,仅仅依据本人 ...