2009年7月24日星期五
drop table if exists in SQL Server
IF EXISTS(SELECT name FROM sys.tables
WHERE name = 'Table_TempForTest')
BEGIN
/* drop table */
DROP TABLE Table_TempForTest
END
GO
/* see all the tables */
select * from sys.tables
powershell can't recognize the vairable setting for dtexec.exe
This script can execute under cmd-line, but not in powershell.....
dtexec /f "C:\Users\Administrator\Documents\Visual Studio 2008\Projects\Integration Services Project1\Integration Services Project1\FileCopyPackage.dtsx" /set \package.variables[User::SourceFileName].Value;"C:\sharefolder\ssiscopied"
2009年7月21日星期二
在64位机上运行dtexec转换Excel数据
- 运行sql server 2000的DTS包
- DTS包中使用的托管的.NET Framework Data Provider或者是本地OLE DB provider不支持64位模式
- 脚本中引用的其它程序集或Com组件没有64位版本或者没有安装64位版本
cd C:\Program Files\Microsoft SQL Server\100\DTS\Binn上面的例子中我们通过参数的形式重新设置了两个连接的值。在dtexec运行时,每个连接都可以通过一个单独的/connection参数来制定新的值,当有多个连接的时候,就设定多个/connection参数,这个好像在文档中没有提到。
dtexec /x86 /f c:\Sharefolder\Package2.dtsx /connection SourceConnectionExcel;"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Sharefolder\BusinessTemplate.xls;Extended Properties=Excel 8.0;HDR=YES" /connection DestinationConnectionOLEDB;"Data Source=.\SQLEXPRESS;Intial Catalog=SSISSampleDb;Provider=SQLNCLI10;Integrated Security=SSPI;Auto Translate=false"
pause
参考资料:
64-bit Considerations for Integration Services
2009年7月10日星期五
2009年6月12日星期五
Entity Framework Tips
Hopefully if you're reading this you've noticed that I've started a series of Tips recently. The Tips will mostly apply to Entity Framework.
Seeing as I expect to have a lots of tips, it probably makes sense to have some sort of index. That is what this is post is, as I add a new tip I will add it to this page too.
If you have any suggested topics for tips please let me know by leaving a comment or emailing me directly at Microsoft (Alexj is my alias at Microsoft, and emails at Microsoft are in the form alias@microsoft.com)
Without further ado here are what I have so far:
Tip 25 – How to get Entities by key the easy way
Tip 24 – How to get the ObjectContext from an Entity
Tip 23 – How to fake Enums in EF 4
Tip 22 - How to make Include really Include
Tip 21 – How to use the Single() operator – EF 4.0 only
Tip 20 – How to deal with Fixed Length Keys
Tip 19 – How to use Optimistic Concurrency with the Entity Framework
Tip 18 – How to decide on a lifetime for your ObjectContext
Tip 17 – How to do one step updates with AttachAsModified(..)
Tip 16 – How to mimic .NET 4.0’s ObjectSet
Tip 15 - How to avoid loading unnecessary Properties
Tip 14 - How to cache Entity Framework Reference Data
Tip 13 - How to Attach an Entity the easy way
Tip 12 - How to choose an Inheritance Strategy
Tip 11 - How to avoid Relationship Span
Tip 10 - How to understand Entity Framework jargon
Tip 9 - How to delete an object without retrieving it
Tip 8 - How to write 'WHERE IN' style queries using LINQ to Entities
Tip 7 - How to fake Foreign Key Properties in .NET 3.5 SP1
Tip 6 - How and when to use eager loading
Tip 5 - How to restrict the types returned from an EF Query
Tip 4 - Conceptual Schema Definition Language Rules
Tip 3 - How to get started with T4
Tip 2 - Entity Framework Books
Tip 1 - How to sort Relationships in the Entity Framework
Enjoy.
2009年6月9日星期二
指定程序运行时的CultureInfo
textBoxMoneyWonFiltered.Text = moneyWon.ToString("C");
假设moneyWon的值是24.1,在不同的Culture下显示的结果会不同,比如¥24.1(中国)或者$24.1(美国)。对于一个支持Globalization的程序而言,设定基于应用程序或者线程的Culture是一个非常好的习惯,可以避免一些怪异的情况出现。
对于一个Windows程序,默认使用与系统相同的Culture设定,要修改程序特有的Culture很简单,只需要在启动的时候添加如下代码:
//覆盖系统默认的 zh-CHS
Application.CurrentCulture = new System.Globalization.CultureInfo("en-US");
2009年6月4日星期四
Linq to SQL要复活吗?
然而令人意想不到的是,微软数据开发组的Damien Guard却突然给出了一个.net 4.0中Linq to SQL的更新列表,涉及到性能、可用性、稳定性、SqlMetal(从数据库生成dbml文件的工具,也可以生成代码)、类设计器和代码生成等方面的问题。
2009年6月3日星期三
当?: 操作遇到可空值类型(Nullable Value Type)
double? PE = String.IsNullOrEmpty(txtHistoryPE.Text)?
null:Double.Parse(txtHistoryPE.Text)
上面的代码试图达到这样一个目的:如果文本框没有输入,则让变量PE保持为 'null' 。但是这段代码无法通过编译,编译错误如下:
Error: Type of conditional expression cannot be determined because there is no implicit conversion between 'null是引用类型,而double是值类型的,?:操作符无法应用到这两者上面。这可能跟大多数人预想的不太一样,一般会认为如果条件为true,那么PE等于null,否则执行Double.Parse的操作并就将结果赋给PE。翻译成代码就是这样的:' and 'double'
double? PE;
if (string.IsNullOrEmpty(txtHistoryPE.Text))
{
PE = null;
}
else
{
PE = double.Parse(txtHistoryPE.Text);
}
但是实际上如果我们真的把代码写成上面那样,恰恰是没有问题的。然而?:操作在这里需要做转型操作,需要对 'null' 做一个转型,将它转换为Nullable
double? PE = String.IsNullOrEmpty(txtHistoryPE.Text)
? (double?) null : Double.Parse(txtHistoryPE.Text)
或者使用default关键字,取double?的默认值null:
值得注意的是这里使用的是default(double?) 而非 default(double),这是因为后者返回0.0,而不是我们所需要的null
double? PE = String.IsNullOrEmpty(txtHistoryPE.Text)
? default(double?) : double.Parse(txtHistoryPE.Text)
2009年6月2日星期二
开始在八一中学体育馆打羽毛球了
昨天终于又去打羽毛球了,这次可能会长久一点,因为一次性订了3个月的场地,每个星期一下午6:00~8:00,两个小时。场地是订在八一中学的体育馆,就在我们公司的楼下,下班直接过去,真近,呵呵。昨天是第一次去那个地方打球,跟公司的3个同事,一个同事的老婆,外加一个微软的哥们,没算错的话应该是6个人一起。八一中学场地是30块一个小时,在北京还算是便宜的了,昨天打了2个小时感觉那的场地还不错,不过灯光稍稍有点暗。
昨天打球用的是亮羽的羽毛球,那是我在北邮门口临时买的,以前经常用的诺地波尔502没有买的。亮羽的球好像没有诺地波尔好用,可是价钱差不多。主要是亮羽的球头太软,打一会头都扁了……
2009年5月26日星期二
Microsoft机器翻译MSDN杂志
"请注意: 从即日起,杂志的内容将由机器翻译为简体中文。得益于这一快速发展的新技术,我们将能够以更多的语言、更快的速度与全世界的读者分享内容。我们欢迎您评估这些页面的翻译质量并提出意见。"
这一段话中提到了“机器翻译”,让我想起还在学校的时候使用过的Google Translation服务,不过我记得当时的翻译结果是惨不忍睹的:)我仔细看了MSDN的翻译结果之后,感觉这次微软的机器翻译基本上也是这个情况。
Google的翻译工具有一个提供修改意见的地方,如果觉得翻译结果不满意,我们可以给出自己的翻译建议。毕竟在现阶段,人的理解能力是机器所无法达到的。这个可以为翻译工具提供建议的特性我在MDSN的翻译结果页面里找到了,请看下面这个图:
note:不过我始终无法正确提交翻译的建议,不知为何……
虽然翻译的质量不怎么样,但是微软已经打算要将机器翻译用作商用了,因为微软不在提供中文版的MSDN杂志,而是让用户阅读由机器翻译成的内容:
请注意,我们将不再提供简体中文版的 MSDN Magazine CHM 文件。然而,您仍可通过单击“HTML Help Format”按钮,以访问美国英语版的 CHM 文件。看来微软是要逼我们为它的翻译机器做出贡献了,呵呵
此为机器翻译内容,社区成员可对其进行编辑。我们十分希望您能单击与以下任一句子关联的“编辑”链接,对翻译进行改进。
2009年5月21日星期四
S#arp Architecture for the ASP.NET MVC Framework
This is a solid architectural foundation for rapidly building maintainable web
applications leveraging the ASP.NET MVC framework with NHibernate. The primary
advantage to be sought in using any architectural framework is to decrease the
code one has to write while increasing the quality of the end productS#arp Architecture adheres to the following key principles:
•Focused on Domain Driven Design
•Loosely Coupled
•Preconfigured Infrastructure
•Open Ended Presentation
Watch the introduction video on dimecasts.net: # 75 - Introdction to S#arp Architecture . It's an opensource project holded on google code.
2009年5月19日星期二
DataBinding无法绑定到public字段上
listBox.DataSource = bindingSource;
listBox.DisplayMember = "propertyName";
上面代码中的DisplayMember就是设置将数据源中对象的哪个属性显示出来。这里需要注意,DisplayMember只能对应到对象的属性(Property)上,不能设置为对象的字段(Field),即使公共字段也不行。对于下面这个对象:
internal class TestBinding
{
public string publicField;
private string publicProperty;
public string PublicProperty
{
get { return publicProperty; }
set { publicProperty = value; }
}
}
如果我们设置绑定:
listSubsetHeaders.DisplayMember = "publicField"; //failed:bind to public field
listSubsetHeaders.DisplayMember = "PublicProperty"; //ok: bind to property
DataBind不允许绑定到公共字段上,必须绑定到属性才行:
One of the reasons why data-binding support is limited to properties happens to
be the fact that all of data-binding is built around PropertyDescriptors and not
on direct reflection.
这里有篇blog讨论了这个问题:Data-binding to public fields... yes or no?
2009年5月15日星期五
Assertion failed: _CrtIsValidHeapPointer(pUserData) in dbgheap.c.
对于托管的C++代码,当它们被编译成dll时,默认情况下是不能使用本地c/c++库(Native c/c++ lib)的,这些本地库包括C run-time(CRT)、ATL和MFC。有这个限制是因为在c++.net 的dll中默认不能使用任何的静态变量(除非是像整形这样非常简单的类型),而这些本地库通常都使用了静态变量。在c++.net项目的属性中默认使用/NOENTRY编译参数来实现这个限制。应用了/NOENTRY参数的DLL项目中不能使用CRT、ATL和MFC,否则就会产生异常。而我的C++ 项目中正好使用了MFC的窗体控件,而且是被编译成DLL的,所以报错了。Assertion failed: _CrtIsValidHeapPointer(pUserData) in dbgheap.c
这个问题的解决办法有两个:
- 在编译时仍然使用/NOENTRY参数,但是禁用自动初始化,改为手动初始化所引用的静态变量 。自动初始化CRT、ATL和MFC有可能会产生死锁(DeadLock)。关于如何手动初始化,请参考Microsoft的技术支持文档“You receive linker warnings when you build Managed Extensions for C++ DLL projects”中的“Resolution”部分。
- 在编译时去掉/NOENTRY参数。 这是非官方的解决方案,非常简单。
我直接使用了第二种方案,问题解决了。如果你也遇到跟我一样的问题,不妨试试看。
2009年5月13日星期三
第一次遇到在多种语言同时出现在一个真实的项目中
记得在刚学习微软的.net时,书上就介绍说.net平台提供了各种语言之间的互操作性,同一个项目了可以使用各种不同的语言进行开发,只要这些语言是.net兼容的。之前一直以为没有人会这么做的,毕竟不同的语言在写法和思维方式上多少会有些差异,而且同一项目的人在阅读别人的代码会很困难,想象一下突然让你去读你不熟悉的语言,还要修改功能是个什么感觉?
我最近就遇到了这样的问题。在接手现在的项目之前,我只用C#,对于C++不怎么了解,更不用说用它来开发了。可是现在的项目在拿到源代码的时候就傻了,真的,不开玩笑,呵呵。你猜怎么地,混合编程啦,c++和c#语言各完成一部分工作,读起来那叫一个痛苦……
你也许觉得是我不怎么会C++的原因,其实不全是这样。同组的同事中有多年C++背景的,不过他们是纯C++的,不是微软的c++.net,看起来也很头大,基本上认同c++.net的行为比较奇怪,呵呵,看来还是不适应。
Anyway,硬着头皮继续看吧……
2009年4月29日星期三
LinqToSql异常:"you can't new an Entity in a query"
public Product GetProduct(int productId)
{
var product = (from p in dbContext.Products
where p.ProductID == productId
select new Product { Name = p.Name, ListPrice = p.ListPrice, ProductID = p.ProductID }
).Single();
return product;
}
原因在于我们在查询语句的最后执行了一个创建新的Product的方法,这就是异常中提到的new entity的意思。在query中我们并不是不能创建对象的实例,而是不能创建“实体”的实例,在linq to sql中实体指的就是与数据库表对应的那个对象,比如这里的Product。
为了验证这一点,我们将Product换成别的类,比如ProductData:
public ProductData GetProduct(int productId)
{
var product = (from p in dbContext.Products
where p.ProductID == productId
select new ProductData {
Name = p.Name,
ListPrice = p.ListPrice,
ProductID = p.ProductID
}).Single();
return product;
}
这样就没问题了,query可以正常执行。ProductData类是我们自定义的类,用作DataContract,不是linq to sql的建模工具从数据库生成的。
所以,如果你得到这个异常,不妨检查看看你的query语句里是否包含了“实体”。
[DataContract]
public class ProductData
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int ProductID { get; set; }
[DataMember]
public decimal ListPrice { get; set; }
}
2007年4月27日星期五
save pictures from MS Word
业务需求
有一个word文档,里面包含了一些构件描述信息和一些图片,要求找出图片另存到一个目录下,然后将该图片替换成一个指示出了图片位置的标签,比如
[img]img/PORT.doc/picture_2.Jpeg[img]
目的是为了在进一步解析构件信息,并存入数据库之后网站的表示层可以直接根据该标签找到图片并显示。
可行的解决方案
既然是要提取图片,那么首先就得在word中找到图片。图片(Picture)在word中会以两种形式存在——Shape和InlineShape——如果要取出所有的图片一定记住不要漏掉了任何一个。但是不是所有的Shape和InlineShape都是picture,我们需要先做判断:
Shape中有两种类型的picture:
MsoShapeType.msoPicture
MsoShapeType.msoLinkedPicture
InlineShape中有两种类型的picture: WdInlineShapeType.wdInlineShapePicture WdInlineShapeType.wdInlineShapeLinkedPicture
找到所有了的图片之后将它们拷贝到剪贴板,然后就可以保存了。基本步骤如下:
1. 首先打开文档。因为要替换图片,那么要求打开文档的时候是可以编辑的——ReadOnly设为false。
2. 读取所有的shape,包括Shape和InlineShape。
3. 读取一个shape判断是否为Picture,如果是则将其选中,并拷贝到剪贴板。
4. 将剪贴板的图片保存到指定目录下。
5. 找到图片在word中的位置,在其前面插入图片标记
6. 将图片从word中删除
7. 继续读取下一个shape
打开文档
oWordApp = new ApplicationClass();
object readOnly = True;
object o_fileName = fileName;
Document wordDoc;
wordDoc = oWordApp.Documents.Open(ref o_fileName,
ref missing, ref readOnly,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref isVisible,
ref missing, ref missing, ref missing, ref missing);
wordDoc.Activate();
读取所有的shape
IList shapes = new ArrayList();
foreach(Shape shape in doc.Shapes)
{
shapes.Add(shape);
}
foreach(InlineShape shape in doc.InlineShapes)
{
shapes.Add(shape);
}
判断是否为Picture
if (isCommonShape)
{
commonShape = (Shape) shape;
isPicture = (commonShape.Type == MsoShapeType.msoPicture ||
commonShape.Type == MsoShapeType.msoLinkedPicture);
}
else if(isInlineShape)
{
inlineShpae = (InlineShape) shape;
isPicture = (inlineShpae.Type == WdInlineShapeType.wdInlineShapePicture ||
inlineShpae.Type == WdInlineShapeType.wdInlineShapeLinkedPicture);
}
选中,并拷贝到剪贴板
if(isCommonShape)
{
commonShape.Select(ref missing);
}
else
{
inlineShpae.Select();
}
wordApp.Selection.CopyAsPicture();
图片保存到指定目录下
System.Windows.Forms.Clipboard.GetImage().Save(fileNameOfPict, ImageFormat.Jpeg);
插入图片标记
object start = oWordApp.Selection.Start; //Shape的起始位置
doc.Range(ref start, ref start).Text = string.Format("[img]{0}[img]", fileNameOfPict);
将图片从word中删除
commonShape.Delete();
完整的代码
public void ProcessAllPicturesOfDoc(Document doc)
{
IList shapes = new ArrayList();
foreach(Shape shape in doc.Shapes)
{
shapes.Add(shape);
}
foreach(InlineShape shape in doc.InlineShapes)
{
shapes.Add(shape);
}
ExtractShape(shapes,doc,oWordApp);
}
public void ExtractShape(IList shapes,Document doc,ApplicationClass wordApp)
{
object missing = Missing.Value;
string pictDirect = "img/" + doc.Name + "/";
int i = 0;
foreach (object shape in shapes)
{
bool isPicture;
bool isCommonShape = shape is Shape;
bool isInlineShape = shape is InlineShape;
Shape commonShape = null;
InlineShape inlineShpae = null;
//check if the shape is a picture
if (isCommonShape)
{
commonShape = (Shape) shape;
isPicture = (commonShape.Type == MsoShapeType.msoPicture ||
commonShape.Type == MsoShapeType.msoLinkedPicture);
}
else if(isInlineShape)
{
inlineShpae = (InlineShape) shape;
isPicture = (inlineShpae.Type == WdInlineShapeType.wdInlineShapePicture ||
inlineShpae.Type == WdInlineShapeType.wdInlineShapeLinkedPicture);
}
else
{
throw new Exception("unknown Shape");
}
if (isPicture)
{
i++;
//select the range of the shape
//Note: the difference between two methods of selection
if(isCommonShape)
{
commonShape.Select(ref missing);
}
else
{
inlineShpae.Select();
}
//compy the picture to clipboard
wordApp.Selection.CopyAsPicture();
if (System.Windows.Forms.Clipboard.ContainsImage())
{
if (!Directory.Exists(pictDirect))
Directory.CreateDirectory(pictDirect);
string fileNameOfPict = pictDirect + "picture_" + i.ToString() + ".Jpeg";
//save picture
System.Windows.Forms.Clipboard.GetImage().Save(fileNameOfPict, ImageFormat.Jpeg);
//insert the img tag just at the start position of the shape
object start = oWordApp.Selection.Start;
doc.Range(ref start, ref start).Text = string.Format("[img]{0}[img]", fileNameOfPict);
//delete the picture
if(isCommonShape)
{
commonShape.Delete();
}
else
{
inlineShpae.Delete();
}
}
else
{
throw new Exception("error occures when copying picture");
}
}
}
}
以上的代码在VS2005+Office2003+xp2下测试通过,
2007年4月4日星期三
MVP: modify data only one time
视图的Getter/Setter主要说的是当视图操作DomainModel(或者与DomainModel对应的DTO-Data Transfer Object)的读取操作,如果使用的Passive View的方式就不用考虑这个问题了,因为这时候的View不存在对Model的操作。
Getter/setter指的什么
为什么在使用Supervising Controller的时候需要考虑?回答这个问题之前需要先说明这里说的Getter/setter分别是什么。比如我们定义一个MyView:
public class MyView{
……
public MyEntity{
get{
return myEntity;
}
set{
myEntity=value;
}
……
}
Getter是指由View提供的对某个实体(Domain Model 或 DTO)对象的读操作,presenter通过getter从view中读取实体的数据然后进行保存;Setter当然就是对应的写操作了,presenter将从model从取出的实体数据传给view就通过setter访问器。所以的这里说的getter和setter都是站在从view的外部看view的这个角度,比如说presenter对view的操作就是这种情况。
存在什么问题?
回到前面的问题,如果一个view同时有getter和setter访问器,presenter把entity通过setter传入view之后,view利用UI的数据对entity进行“更新”。注意这里的更新有两种做法,一种是由view将修改过的entity传给presenter,由后者进行更新,一种是view直接调用model中相应的方法进行更新,不要忘记了Supervising Contrller中view对model有一定的了解,它可以操作model。当采用第二种方式更新的时候view对model的了解太多,会导致view和数据访问层的联系过于紧密。初此之外,还有一个问题同时存在于这两种更新方式之中,就是对entity的修改可能既存在于view也存在于presenter,散布在多处的修改很容易导致不一致性,而且难以理解和维护。
DTO的好坏
使用DTO可以解决上面的问题。DTO是用于在层与层之间传递数据的一种中间数据形式,它的数据来自domain object,它的使用使得view不再依赖于具体的domain model,因为这时候的view只需要知道与presenter之间通讯的数据形式即可。使用DTO之后,view端得到和修改的都是DTO,presenter从view获得DTO之后进行一次DTO-Domain Object的转换,最后将转换得到的domain object存入数据库。这样一来view和presenter的分工明确,而且view对dto的修改不会影响到最终数据,因为真正的数据修改实际上只在presenter内部发生,dto只是做了临时存储之用。
使用dto也有它的坏处,如果系统很大,domain object很多,那就意味着DTO需要很多,对domain 的修改都要相应地在dto上实施,使得维护工作量变大。
用Update Method 替代Getter
这是Billy McCafferty在他的blog中提出来的方法。他提出保留view的setter访问器而“去掉getter访问器,其而代之的是由view提供一个Update方法”。这样view既可以从presenter处得到domain model的数据,又可以利用自己的Update方法更新来修改这些数据以反应用户的输入。注意这里的“Update”并不是写回了数据库,而只是是修改了Domain object的数据,还存在于内存中。以保存操作为例,基本步骤如下(Presenter:表示都是发生在presenter内部):
Presenter: view.Update(myEntity)
Presenter: presenter.Validate(myEntity)
Presenter: customerDao.SaveOrUpdate(myEntity)
可以看出对数据的修改发生在view,而presenter内部只是进行逻辑控制和数据验证。
总结
实际上DTO方式和Update Method 方式的目的都是为了控制对数据的修改,严格限制数据的修改只发生在一个地方不至于散布在各处,让view和presenter分工明确,理解和维护起来都容易。
我比较喜欢Passive View方式,因为View和Model完全分开,但是有时候Supervising的方式也很不错,比如在winform中要使用数据绑定,在asp.net中要使用Object Data Source,用后者就很方便。总之没有绝对的好坏,视情况而定。
2007年4月2日星期一
Removing the "Legacy" from your code
什么是遗留代码(Legacy Code)?
"To summarize, Legacy Code is code that is difficult, inefficient, or risky to change, but too important and useful to throw away or ignore. You have to deal with it. You can ignore it and keep going, but it might be a lot smarter to pay down the Technical Debt that you've accrued to remove friction in your development environment."
“Legacy” 在计算机领域的意思是“【电脑】(用于过时的软件或硬件产生的数据等) 遗留下来又难以更新的; 老化的”。Legacy code是指以前的系统中遗留下来的那些难以理解,效率低,而且维护起来有风险的代码。遗留代码通常很重要和也很有用,我们不能弃之不用。
遗留代码有什么可怕的?
- 代码没有经过自动化测试。且不说缺少测试本身就是个问题,更重要的是没有自动化测试也意味着系统可能是无法测试,比如系统各部分的耦合度太紧密。“MVP vs MVC”提到过的Asp.net的CodeBehind方式,由于业务逻辑的代码都嵌入到了UI中,要想自动测试的困难很大。
- 代码很难懂。比如结构不够清晰、一个方法需要你PageDown好几下才能看完、命名乱七八糟毫无意义(会带来误解的更让人头疼)、缺少必要的注释等等。
- 难以调式。这是后继开发人员和维护人员很头疼的问题,为什么这段代码会出现异常呢?为什么输出的结果不对呢?这报的个什么错误啊,完全猜不出来是从哪里抛出来的……
- 部署困难。系统对环境的依赖不够清楚,代码又难懂又难调式,部署起来能容易吗?
- 反馈慢。这是自然的了,因为你要把代码编译通过都很困呢,加上测试和部署的过程又困难重重,那将是一个很长的过程。
也 许是程序员经验不足,也许是工期太紧而没时间顾及代码的质量,这些都是导致遗留代码存在的因素。已经存在的遗留代码我们只能硬着头皮去改了,但是千万不能 让我们现在正在编写着的代码又变成遗留代码了,在设计开发的过程中一旦意识到技术性问题的存在就应该尽早地进行更正,包括更新设计和重构代码,如果让它们 存在的越久那以后的修改就越困难。
"Constant small refactorings improve efficiency. Waiting too long and making a refactoring expensive is inefficient. In other words, don't allow technical debt to build up, and avoid Michael's "Tipping Point." The interest rates from Technical Debt are a killer.
2007年4月1日星期日
the order of attributes reflected
更正一个错误
在前一篇“Upgrade to support multi validator attributes”中的最后一个部分中提到需要注意反射得到的属性之间的先后顺序问题:
需要注意的地方
[RegularValidator("^[0-9]$")]
[RequiredField]
private System.Windows.Forms.TextBox textBox2;
看两个属性的位置,它们不是随意摆放,要求属性之间有一定的先后顺序。这主要是因为MemberInfo.GetAttributes方法返回的属性是按一定的顺序排序的,当我们在注册事件的时候,会按照GetAttributes返回的属性数组中属性的先后顺序来为控件注册属性中的ValidatingDelegate。具体的顺序是这样的:
“离属性的目标字段(也就是要附加属性的字段)越近的属性,在反射得到的集合中位置越靠前”
也就是说如果我们要让某个属性最先起作用,那么就得让它里目标字段最近,比如例子中的RequiredField。
可 是后来我发现把UserControl加入之后,Form中属性的顺序是遵从上面说的,但是UserControl的属性似乎不是那么严格。在发表这篇文 章的时候测是通过的,可是在我昨天的使用中发现UserControl中不正常的问题,不知道是因为环境的不同还是其他的什么原因。但是不管怎么样我们总 是不能依赖属性的顺序了,为了不造成误导,我必须将上面的话删除。
新的解决办法
在不能依赖属性的顺序的情况下,如何保证附加到控件上的属性所做的限定都能得到满足呢?根据上一篇中的阐述,一个属性实际上是对应到了控件的一个Validating事件,要限定属性的顺序主要是为了限定控件的Validating执行的顺序。
Validating事件乱序带来的问题
还是使用前面的例子,要给一个TextBox加2个输入的限定:
[RegularValidator("^[0-9]$")]
[RequiredField]
private System.Windows.Forms.TextBox textBox2;
我们希望这个文本框的内容不为空而且是0-9之间的一位数字。我们希望的是先执行RequiredField的检查,然后执行RegularValidator("^[0-9]$")的检查。但是如果乱序话,有可能RegularValidator("^[0-9]$")的检查在前面,这样会产生什么问题呢?比如我们输入的是“not empty”:
protected void Validate(object sender, CancelEventArgs e)
{
e.Cancel =(!IsValid(sender));
}
上面的检查会执行两次。RegularValidator先执行,由于输入不是0-9的数字,所以检验失败:
e.Cancel=false;
然后RequiredField执行,由于输入不是空的,检验合法了:
e.Cancel=true;
看 起来似乎两次检验是两个事件,不会互相影响,但是实际上不是这样的。事件注册使用的操作符"+="实际上是将同类型事件的多个处理者 (EventHandler)串成了一个链【参考Delegate.Combine(params Delegate[] delegates)】,一旦数据源出发一个事件这个链中的处理者会相继得到事件的引用然后进行各自的处理,这也是多播委托的特性。这么说来虽然是进行多次的处理,但是它们处理的仍然是同一个事件的引用。 回到例子中,虽然是两次检验,但是是对于文本框的同一次触发事件的处理,也就是说它们处理的“e”是同一个对象。这样看来“e.Cancel”的值是先被 修改为false,然后又被置为true,那么检验应该是合法的。实施也证明了输入“not empty”的时候不会报告检验失败。
不依赖验证顺序
乱许的时候出现错误,究其原因主要是后面一次的处理覆盖了前面的处理结果,只要我们在后一次的验证中把前面已经得到的结果一起验证就可以了。对Validate处理代码稍作修改即可:
protected void Validate(object sender, CancelEventArgs e)
{
//should pass every validation
e.Cancel =(e.Cancel|| !IsValid(sender));
}
现在的验证要想pass,除了满足本次检验以外以前的检验也必须是pass的。这样的修改不再依赖于验证的顺序。
2007年3月30日星期五
Upgrade to support multi validator attribute
简介
昨 天我在“validate a control with only one line code”中介绍了如何利用属性来实现类似asp.net中的validator的功能,今天花一些时间将这一功能进行升级,使得它可以支持对一个控件使 用多种检查,类似在asp.net中可以同时给一个控件使用多个validator。
[RegularValidator("^[0-9]$")] //attribute的尾巴可以省略
[RequiredField]
private System.Windows.Forms.TextBox textBox2;
这样检验一个textbox的值不可为空并且满足正则表达式的约束,是不是很简单也很清楚呢?
详情见我在Spaces.Live上的文章: “Upgrade to support multi validator attribute” on Spaces
blog spot好了才两天又挂掉了:(