常见错误

是否可为空

编号A-001

描述 新增了字段,逻辑上是必填的。但为了照顾历史数据,设置成了非必填。当页面访问历史数据时会报错。

样例 Attachment表新增了Uploadedby字段,类型为User。新添加的记录这个字段不会为空但历史记录这个字段是空的如果不对此字段做可为空的判断,那么访问Attachment.Uploadeby.Name就会报错

解决方案 将此新字段定义为不可为空,按一定规则初始化历史数据这个新增字段的值例如如果是订单附件都初始化成订单的采购员

 

编号 A-002

描述 要将原本必填的一个字段改为非必填,但已有的程序对这个属性的访问都是假定它是不可为空的而不做判断当新添加的数据这个字段为空时就会报错

样例 系统“提交”这个字段,会保存当前登录的用户,因此是不会为的。用户提出登录的用户其实只是代替真正的提交人录入数据,因此希望将此字段设为空。

解决方案 另外增加一个“提交人姓名”栏位(不可为空),用来保存真正的提交人历史数据可初始化为提交人的姓名。扫描所有“提交”的引用,如果是要显示提交人的姓名(即Requestedby.Name),改为新的“提交人姓名”,如果要引用提交人的其它属性保持不变。

 

编号 A-003

描述 对象可为空但在Lambda表达式中未判断。

样例 .Where(x=>x.User.Id==1) 如果User对象可为空,在C#执行可能会报错。这个错误在开发新程序时也经常,因为在SQL时是不需要判断的所以会习惯性地以为Lambda表达式也不需要判断。

解决方案 改为 .Where (x=>x.User!=null && x.User.Id==1)

 

编号 A-004

描述 页面提交没有反应,客户和服务器端都没有执行

原因 页面上隐藏的编辑控件对应的VM属性设置了不可为空

解决方案 VM属性如果可为空一定要设置为可为空的属性特别是对于字符串属性一定要设置为string?

 

编号 A-005

描述 可为空的附件字段,如果不传附件提示 ”” is not valid

原因 附件对应的控件通过ng-show隐藏

解决方案 使用ng-if设置

 

 

 

如果未上传附件,则div标签内的控件在HTML中不存在也就不会上传;如果使用ng-show,控件仍然存在Temporary字段bool就会报错(空字符串无法转换成bool)

 

四则运算

编号 B-001

描述 SQL存储过程和C#中,两个整数相除结果仍然是整数除非明确做类型转换VB两个整数相除会根据结果对应的变量类型做转换

样例 C#  int a=1; b=2;

decimal c = a/b;  c的值是0

     VB  declare a as integer=1

         declare b as integer=2

         declare c as decimal = a/b    c的值是0.5,因为c定义为decimal

 SQL declare @a int

         declare @b int

         set @a=1

         set @b=2

         Select @a/@b 结果是0

解决方案  C# decimal c = (decimal)a/(decimal)b; //ab强制转换成decimal

SQL: Select (@a*1.0)/(@b*1.0) –@a@b强制转换成浮点数

 

编号 B-002

描述 Javascript对浮点数进行四则运算后会出现很多位小数

样例 var a =0.1+0.2; //a的值为0.30000000000000004

解决方案 使用numeral.js库进行四则运算

var a = numeral(0.1).add(0.2).value();//a的值为0.3

 

编号 B-003

描述 Javascript toFixed方法四舍五入的结果不正确

样例 0.355.toFixed(2); //结果0.35而不是0.36

解决方案 使用numeral.js库进行格式化

numeral(0.355).format("0.00")

 

多线程并发

编号 C-001

描述 Reader引起性能下降

原因 Reader采用Builder模式构建查询条件执行查询后清除查询条件如果ReaderCastle容器中配置为单例模式(缺省情况),多个进程会使用同一个查询条件,就会出现A进程构建的Filter,B进程清除A进程执行查询时实际是要查询所有的记录从而引起内存瞬间被大量占用

解决方案 ReaderMVC框架下已经被配置成瞬时模式WebForm程序改造时如果添加新的Reader,那么在CastleComponent.config中一定要定义为瞬时模式

 

编号 C-002

描述 日志显示Processor处理的单据与传入的参数不同

原因 Processor定义了局部变量如果被配置成单例模式多个进程有可能互相覆盖此变量

解决方案 ProcessorMVC框架下缺省是单例模式如果使用了局部变量则一定要定义成瞬时模式WebForm程序改造时也要在CastleComponent.config定义为瞬时模式

 

编号 C-003

描述 单据的审批流程出现了不符合流程的处理步骤或者出现重复的步骤

原因 

用户点击按钮,由于处理速度较慢,用户以为没有点上会再点一次,导致对同一个单据处理了2遍;

用户打开页面时按钮是可见的在点之前其用户对此单据做了处理此时已经不能再点此按钮了但程序没有验证

两个用户都可以处理如果点击按钮时间比较接近那么验证都是可以通过的导致同一个单据被两个用户处理

解决方案 

MVC框架

1 必须验证当前状态下可处理此单据

2 单据对应的表增加RowVersion字段对象继承VersionedDomainObject,保证读入的版本号和保存时是一致的

3 保存新数据的时候要添加[PreventDuplicate]属性防止添加重复的数据

WebForm

1 必须验证当前状态下可处理此单据

2 单据对应的表增加RowVersion字段hbm文件增加version配置保证读入的版本号和保存时是一致的

3 处理StaleObjectException

 

编号 C-004

描述 多个进程更新了不同的单据,这些单据关联了同一个对象,而这个对象只更新了一次。

举例 发货通知对应多个包装箱,每捡一个箱子更新发货通知的已拣货数量,全部拣货完毕更新状态为已拣货但偶尔会出现全部拣货完毕已拣货数量少于应拣货数量,状态仍然为部分拣货

原因 当两个终端(PDA或者Web网页)捡此发货通知的不同包装箱时得到的已拣货数量是相同的两个进程分别更新已拣货数量则只有后一个会保存到数据库中

解决方案

1 不要通过对象导航直接得到关联对象而是通过Dao.GetbyId(Id,true)得到传递true参数保证了在第一个事务Commit之前另外一个进程只能等待这样保证了另一个进程得到的是更新后的关联对象

2 不推荐直接更新关联对象的数量

DNItem.PickedQuantity+=box.Quantity; //不推荐

      而是重新找到已拣货的所有箱子的数量并求和

        DMItem.PickedQuantity = Sum (DNItem的已拣货箱子数量) //推荐

      这个办法不能从根本上解决问题但只要发生问题的两个进程不是在捡最后两个箱子那么后续的操作有机会将PickedQuantity更正

 

失效数据

编号 D-001

描述 数据已经设置为“已删除”,“已取消”等状态,但在相关页面仍然作为备选项,或将相关的数量纳入了统计和汇总范围

 

编号 D-002

描述 业务表中存储的是已删除的基础数据,当显示业务数据时,列表中没有相应的项导致报错或无法显示存储的值

解决方案 

传到客户端的业务表数据不但要包括基础数据Id,还要包括描述判断清单中是否有业务表的基础数据Id,如果没有则直接添加一条已删除的记录AngularJs也可以使用RefSelectEnforceMatch过滤器来实现

 

编号 D-003

描述 清单页面选择的记录状态已经变化,但服务器端未判断是否仍然可操作

解决方案 

服务器端对客户端提交的数据总是需要验证状态是正确的

 

编号 D-004

描述 判断单据是否可做某种操作时未排除已失效的记录

样例 清单页面已删除的数据仍然可以编辑

 判断是否存在重复记录时未排除已删除或已取消的单据

 

数据库性能

编号 E-001

描述 对子表数据查询非常缓慢

原因 缺省情况下SQL Server会对主键字段建立聚簇索引对于子表最常见的查询就是根据父表Id查询所有记录如果记录不是一起插入的那么这些记录的物理存储位置就是不连当记录数达到一定量级之后就会有大量的硬盘操作导致性能下降

解决方案 修改聚簇索引为 父表Id + 子表Id或者父表Id + 逻辑主键(比如ItemNo),这样同一个父Id的记录的物理存储位置是连续的,可以大大加快查询速度。

 

编号 E-002

描述 字符型日期型字段的查询非常缓慢

原因 字段上缺少索引

解决方案 字符型日期型字段加索引。注意不要对布尔型,数值型索引。字符型字段索引对于 like ‘%abc%’ 这样的查询不起作用的只对 like ‘abc%’ 这样的查询作用。

 

编号 E-003

描述 自定义函数只能出现在Select,不能出现在order by 或者where 否则会引起全表扫描

解决方案 主表上添加冗余字段将函数的计算结果保存下来。例如订单需要按金额排序,如果定义一个函数dbo.fnGetPOAmount通过求POItem金额之和来实现排序则会非常缓慢。解决方法是在PO上增加一个TotalAmount字段,每次更新POItem订单行金额之和保存到字段上,排序是按此字段排序即可。

 

字符编码/大小写

编号 F-001

描述 未正确区分到底是数据库查询还是内存中查询导致大小写比较不正确

样例 查询1 Dao.GetbyQuery(x=>x.Name==”Abc”)

     查询2 Dao.GetbyQuery().Where(x.Name==”Abc”)

     查询1是在数据库中查询存储的ABC,abc都能查得到数据库查询不区分大小写

     查询2 是在内存中查询只有Abc才能查得到。C#区分大小写

解决方案 对于Code,LoginId之类的关键字无论用户输入的是什么,总是转成大写再存储

 

编号 F-002

描述 表中的中文字符存储为?

原因 用户的SQL Server安装的是英文版创建的表所有的字符型字段的collate也只支持英文(仅仅改类型为nvarchar,ntext不起作用

解决方案 修改数据库的缺省的排序规则为中文(Chinese_PRC_CI_AS),如果不能修改,创建表时指定字符型字段的排序规则为中文

 

编号 F-003

描述 多个数据库表做连接查询,要对不同数据库的字符型字段做比较但各自的排序规则不同连接时会报错无法解决排序规则冲突。这种情况通常是因为其中一个数据库是客户的其它系统在使用,无法修改排序规则。

解决方案 所有字符型字段的比较总是将不是中文的字段强制转换成中文排序规则

数据表DB1.Table1.Field1Chinese_PRC_CI_AS

数据表 DB2.Table2.Field2 SQL_Latin1_General_CP1_CI_AS

以下语句会报错

   Select *from DB1.Table1 Inner Join DB2.Table2 on Table1.Field1=Table2.Field2

应该改成

   Select *from DB1.Table1 Inner Join DB2.Table2 on Table1.Field1=Table2.Field2 collate Chinese_PRC_CI_AS

 

编号 F-004

描述 存储过程中创建的临时表与物理表的字符型字段做比较会报错。

原因 用户的数据库系统是英文创建的临时表缺省排序规则也是英文,而物理表是按中文排序的

解决方案 创建临时表时指定排序规则为中文

   CREATE TABLE #New

    (m_sID NVARCHAR(4) collate Chinese_PRC_CI_AS)

            

编号 F-005

描述 bulk insert提示文件格式不正确但用Excel打开文件发现格式没问题

原因 文件中含有中文或其它双字节字符

解决方案 SQL Server 2016及以上版本,bulk insert支持CodePage=65001可导入UTF8文件。如果是低版本SQL Server需要用c#程序将文件转化成ANSI格式再导入。

 

编号 F-006

描述 读入文本文件的内容有乱码

原因 文件中含有中文或其它双字节字符但打开文件时未指定encoding.

解决方案 打开文件时指定encodingutf-8.

 

编号 F-007

描述 生成的文本文件的内容有乱码

原因 文件中含有中文或其它双字节字符生成文件时未指定encoding.

解决方案 生成文件时指定encodingutf-8

全局性修改

编号 G-001

描述 增加全局性的FilterModule,或者修改Service的调用方式(例如http改为https,某个关联的前端无法通过Service调用

解决方案 Web之外的其它系统(包括Windows客户端Windows服务小程序App,第三方的系统调用的服务需要在服务的说明上标注做全局性修改时必须查找所有这样的方法修改关联的系统或通知第三方系统开发厂商

 

编号 G-002 表或字段重命名后存储过程视图或自定义函数无法执行

解决方案 把所有的存储过程,视图,自定义函数重新生成脚本,报错的就是在使用原来的表名或字段名,修改为正确的即可。

 

编号 G-003表或字段重命名后某个前端程序无法运行 

解决方案 对应的DLL没有更新,重新编译后重新发布一遍即可。

 

文件操作

编号 H-001

描述 某个文件夹写入文件,但实际并没有这个文件

解决方案 总是要判断文件夹是否存在,不存在要先创建。特别注意如果文件夹是多级,要逐级判断和创建

 

编号 H-002

描述 要新建一个文件,文件已经存在

样例 文件处理完毕会监视目录移动到bak目录,但在生产环境下用户可能bak的文件重新复制到监视目录,程序处理完毕再次移动到bak目录就会报错。

解决方案 不要假定目录下一定没有这个文件,即使文件名是用GUID命名的。大部分创建文件的方法都支持是否覆盖已存在的文件,或者方法本身就是自动覆盖的。如果方法不支持,则需要判断文件是否存在,存在就要先删除再创建。

 

编号 H-003

描述 多线程环境下试图打开一个尚未写入完毕的文件,此问题通常出现在Service或有定时器的程序中。

样例 A进程通过网络接收到一个文件并保存到一个目录;B进程通过定时器或者FileSystemWatcher监控此目录有文件的话会处理此文件如果A进程尚未保存完毕(比如通过FTP方式接收文件会先创建文件然后通过流向文件中写入字节流),B进程打开文件会报错。 

解决方案 打开文件必须截获IOException,出现此错误可等待下次再处理。

 

编号 H-004

描述 导入程序用上传控件传了一个文件后,无法传下一个文件。

原因 导入程序的上传控件设置了multi=false属性,上传后没有清空控件对应的ng-model属性

 

Https访问

编号 J-001

描述 PDA无法访问基于Https的服务

原因 服务器端返回的Https证书信息PDA无法验证是否合法

解决方案 强制PDA认可服务器端返回的证书

public class TrustAllCertificatePolicy : ICertificatePolicy

    {

        public TrustAllCertificatePolicy()

        {

        }

 

        public bool CheckValidationResult(ServicePoint sp,

            X509Certificate cert, WebRequest req, int problem)

        {

            return true;

        }

}

 

在程序启动时调用如下

System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();

 

编号 J-002

描述 Windows客户端无法通过生成的客户端代理访问基于Httpsasmx或者wcf服务

解决方案必须通过配置文件告知代理类通过Https访问服务器而不是仅仅修改URL

    <bindings>

      <basicHttpBinding>

        <binding name="PrintServiceSoap"  maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" >

          <security mode="Transport"></security>

       </binding >

      </basicHttpBinding>

</bindings>

 

编号 J-003

描述 Javascript无法访问另外一个域名下的服务

原因 Javascript不允许跨域访问其他站点的服务除非此站点明确允许

 宿主站点与访问站点的分别使用httphttps

解决方案 

1 启用跨域访问 在被访问站点的Web.config中配置允许的域名

<system.webServer>

    <httpProtocol>

      <customHeaders>

        <add name="Access-Control-Allow-Origin" value="http://www.abc.com"/>

        <add name="Access-Control-Request-Method" value="GET,POST,OPTIONS"/>

        <add name="Access-Control-Allow-Headers" value="Content-Type"/>

      </customHeaders>

    </httpProtocol>

  </system.webServer>

2 宿主站点和被访问站点使用相同的Scheme

将其中一个站点升级为https或降级为http

如果无法升级或降级JS中访问改为在服务器端访问

 

编号 L-001

描述 URL不完整

原因 URL中包含特殊字符

解决方案 

可能包含特殊字符的值调用encodeURIComponent做转码

Javascript生成的URL如果包含特殊字符也要做encodeURIComponent转码

如果URL在服务器端生成则需要调用URLEncode方法转码

注意以上方法只对包含特殊值的字段做转码而不能对整个URL做转码因为它会把&=等正常字符也转码

正确 url=”a.aspx?a=” + URLEncode(b)

错误 url = URLEncode(“a.aspx?a=” +b)

 

编号 L-002

描述 在循环定义多个控件的事件处理程序事件处理程序实际执行时接收的参数不正确。

样例

for(var i = 0; i < 10; i++) {

        $("btn"+ i ).click = function() {

          console.log("This is element #" + i);

        };

}

上述代码点击任何一个按钮都会显示This is element #10,而不是期待的那样点不同的按钮提示不同的序号

原因 在循环的过程中只是注册了一个事件处理程序,只有到点击的时候才会计算i的值,而那时循环已经结束,i的值总是10

解决方案

for(var i=0;i<=10;i++){

 function(k){

  $("#btn"+k).click(function(){

   console.log("This is element #" + k);

})

 }

}(i); //立即执行

将整个for循环做一个函数每次立刻传递i值并执行。

 

编号 L-003

描述 程序在Chrome下运行正常360浏览器直接报错

原因 360浏览器对ES6的支持不完整,let,箭头函数不支持

解决方案 开发前与客户沟通使用的浏览器目前尽量不要使用ES6的语法

 

编号 L-004

描述 传递给清单页面的搜索参数不起作用

样例 

def.ContentState('List', "PartNo ") 定义一个State,传入PartNo参数

c.Filter = {PartNo: $stateParams.PartNo} 将传入的PartNo赋值给c.Filter

@Html.KendoTextBoxFor(m => m.Input.PartNo).NgModel("c.Filter.PartNo") UI界面文本框绑定c.Filter.PartNo

c.DataSource = kendoui.KendoGridServerDataSource("c.frmSearch", [{ field: 'PartNo', dir: 'asc' }], 15); 调用服务器端的Search方法会发现PartNo没传到服务器文本框显示的值是正确的

原因 搜索控件还没来得及绑定传入的值就被提交到服务器

解决方案

待页面加载完成再执行Search方法

$scope.$on('$viewContentLoaded', function () {

   c.DataSource = kendoui.KendoGridServerDataSource("c.frmSearch", [{ field: 'PartNo', dir: 'asc' }], 15);

}

 

编号 L-005

描述 支持StatefulGrid的表格Search方法被调用了2

解决方案 StatefulGrid会在框架内部调用Search方法并绑定到表格上因此表格的autoBind应该设置为false.

 

编号 L-006

描述 提示用户是否继续,用户选否后仍然继续执行了操作

解决方案 操作应该放到pbui.Confirm的回调中执行

 

编号 L-007

描述 符合某种条件需要提示用户是否继续,用户确认后才继续执行;不符合条件时应该直接执行,但实际根本未执行。

解决方案 

 

 

 

 

程序部署

以下问题与程序书写无关而与部署方式有关。当系统需要访问某个资源时,运行账号必须对此资源有读或者写权限。特别是当资源位于其它服务器时,一定要配置系统以一个AD账号运行这个账号不能是某个具体用户的账号而必须是为此系统申请的专用账号(特定用户的账号通常都有密码过期策略,用户修改密码后程序就无法运行了),此专用账号对要访问的资源有对应的权限。

常见的系统需要配置运行账号的情形

1 IIS Application Pool

2 SQL Server Service

3 Windows Scheduled Task

4 定义的Windows服务

编号 I-001

描述 文件存在,但用File.Exists判断却显示不存在

原因 当程序的运行账号对目录没有读权限时,即使文件存在File.Exists也会返回false

解决方案

目录位于Web服务器要保证Everyone账号对此目录有读权限

目录位于其它服务器IISApplication Pool配置为专用账号

 

编号 I-002

描述 bulk insert无法导入csv文件

原因 SQL Server的运行账号对csv所在服务器没有读权限

解决方案 

设置SQL Server的运行账号为专用账号如果csv文件是其它系统放上去的则只能采取这个方案

SQL Server 2016及以上版本支持OPENJSON函数可以通过Web程序把用户上传的文件转换成JSON格式然后通过存储过程实现导入此方式传递的JSON通常都很大在存储过程端要将参数类型定义为nvarchar(max),调用端一定要设置参数类型为NHibernate.NHibernateUtil.StringClob否则会出现字符串被截断的问题

 

编号I-003

描述 定时任务对应的程序直接运行没问题,但在定时任务中执行时报错。

原因1 定时任务对应的账号没有访问相关资源的权限

解决方案 定时任务要特别注意不能配置为管理员或者某个特定的AD用户的账号运行。如果需要访问网络资源则配置为专用账号如果不需要则配置为SYSTEM账号。如果只需要访问本地资源的话,也可以在服务器上创建一个本地账号,设置密码永不过期,然后配置服务以此账号来运行。

原因2 程序的运行目录没有正确设置如下图应该在“起始于”处写上程序所在目录。

 

 

 

杂项

编号 K-001

描述 清单页面点标题排序报错

原因 字段本身不可排序或未转换成Domain的属性

解决方案 由于KendoGrid只能将列对应的VM属性作为排序字段VM属性有很大可能并与Domain的属性直接对应因此在Search方法中必须调用AppBaseController. GetConvertedSortString将其转换成Domain属性

 

以下情形不可排序需要在客户端指定对应的列sortable=false

  1. 备注型字段
  2. Domain的扩展属性
  3. Domain的集合属性 例如Items.Count

 

编号 K-002

描述 审批/处理流程发生了变化,已经按原流程进行了一半的任务无法按新流程进行。

解决方案 

在启用了eBPM的流程中新的流程会生成新版本并只对新产生的任务生效

如果未启用eBPM, 则需要检查每个进行中的任务通过后台调整Process表使其符合新流程

如果审批处理的相关功能在独立的类库可以将此模块复制一份(启用不同的命名空间),并在此复制的版本上修改。上线新流程之前提交的任务仍然使用旧流程。

 

编号 K-003

描述 删除/重命名Domain的属性相关查询报错或页面无法显示相关字段的值

原因 HQL查询运行时才检查属性是否存在,编译无法检查

 DataGrid绑定到Domain的属性运行时才检查是否存在编译无法检查

 VM属性没有同步更新导致修改后的Domain无法自动映射到VM

解决方案 

 全局搜索修改之前的属性名逐个确认是否需要修改

 

编号 K-004

描述 单一表拆分为子表,原本在主表上的属性在无法显示

原因 没有修改DomainVM的映射导致VM属性为空

 

posted on 2022-03-18 10:57  蒙蒙浮霁月  阅读(263)  评论(0编辑  收藏  举报