【翻译】数据转换和验证
摘要
这篇文章介绍了一个关于数据转换和验证时的技巧、遇到的问题。Brian(作者)检查数据和 Nullable 类型转换中涉及的技术,以及相关的C# 代码。他还提供了一个 String 概要,Data Type 范围,和在 ASP.NET 中使用不同的方式限制 input 数据。
by Brian Mains
介绍
数据类型转换不总是会隐式发生的。为了避免编写大量代码,我已经包括一种用来分析一定范围的数据值的技巧。我也讨论了关于一些总所周知的数据项的转换和验证。
数据类型转换
当从一个外部资源(例如:一个文件)传入数据时,需要一个解释数据内容的实体类。例如,在数据库系统中,.NET 的每个数据具有一个映射到他们的 SQL Server 或 Oracle 数据库等效的数据类型。
当从一个组件(流读取或者数据表)读取数据时,传入系统的数据值可能为1。尽管他是一个数字,基础数据类型可以是一个string,然而 "1"(字符串) 不等于 1(数字) 。获得一个 "1" 的正确类型再进行对比。int.Parse 或者int.TryParse 方法可以将值转换成数字类型。
有时一个表的数据使用了许多不同类型(例如:Profile 表),所有的值都存储在一个 Nvarchar 或者 Varcher 数据列中。因为 (Nvarchar 和 Varchar) .NET 中的基础类型是 string,而存储的这些值不仅仅只转换成 DateTime 或者 Int 类型。只要存储的值是有效的 string 类型,使用 DateTime.Parse 或者 int.Parse 进行转换(使用 TryParse 进行转换可以防止异常发生)时,就有可能会引发异常。
在执行类型转换时,我将使用一个 Helper 的方法将值进行相应的类型转换。例如,假设从一个基础集合(从文件或者数据库中填充的集合)中你使用 Key 来检索一个项,使用下面的方法将数据动态的转换为泛型类型。
Listing 1
然后从一个集合中获取一个值,首先检查确保该数据不是 null ,也不是数据库中的 null 。如果是这些值,返回默认值(T)。这里返回指定的类型默认值是为了不会因为类型的不同而引发异常。例如类型 null 被返回。而对于值类型,最小值没有被返回 (整数返回 0)。
接下来的一步判断基础 Value 是否是指定类型。如果 Value 完全匹配泛型类型,它将被返回。
Listing 2
当返回泛型类型时,有时 User 想要一个 string ,不考虑基础类型是 Int,Decimal,DeteTime,等等。下面展示了一个绕过编译器错误的小技巧。你不能简单的去调用 ToString() 来返回 Value。即使该类型 在您的逻辑中 匹配,编译器不知道这种情况将始终为一个字符串,因为编译器只处理泛型类型 T ,并不知道基础的表示形式。
Listing 3
object stringValue = value.ToString();
return (T)stringValue;
对于所有类型转换,可以使用 Convert.ChangeType 来将对象转换成相应的类型。DateTime,string,这样的大多数值类型支持使用 Convert.ChangeType 进行类型转换。但是,在调用之前确保可以进行类型转换,因为如果转换不成功将引发异常。
我发现在这个方法中的一个变化:Null 类型。Null 类型不支持使用 Convert.ChangeType 进行转换,并且我想到下面一种替代方法。
Nullable 类型转换
在C# 中 Nullalbe 类型表示为 <type>?,如 int? 。像 int? 这样的许多参数和字段类型表示为支持一个直接返回 null 的 Value。此外,Null 对象的定义添加了两个其他属性:HashValue 和 Value。如果该 Value 是 Null 时,您访问这个 Value 属性,将引发异常,所以您要检查这个 HasValue 的值不为 Null 。另一种方法是调用 GetValueOrDefault() 方法。
Nullable 类型用泛型表示为 Nullable<int>,使用下面的语法构造一个新的 Nullable 对象。
Listing 4
new Nullable<int>(1);
Nullable 值将转换成基类型作为引用类型被传入,这必须进行特殊处理。在以前这样做是不起作用的。若要确定是 Nullable 类型,使用下面的方法。我必须感谢微软;我曾在检查 JSON 序列化进程的代码时因为 Nullable 而报错。
Listing 5
理想情况是使用 Null 类型的构造函数,但是我们需要提取泛型类型并传入一个新的 Nullable 值。例如,下面的代码中,Nullable<int> 是一个引用类型,Generic Argument 引用一个值类型。
Listing 6
Type nullableType = typeof(Nullable<>).MakeGenericType(parameterType);
return (T)Activator.CreateInstance(nullableType,
new object[] { Convert.ChangeType(value, parameterType)});
创建一个 Nullable 类型的实体,获取 Nullable<> 类型,使用 MakeGenericType 方法创建一个 Nullable<int> 。 CreateInstance 创建 Nullable 类型的实例,安全的使用 Convert.ChangeType 将 Value 转换成 Int 类型。
前面已经讲到,因为 Nullable 类型不能转换,我们不能简单的使用 Convert.ChangeType(value, Nullable<int>) 来进行转换。那将会抛出异常。要是想处理这种情况,使用 GetGenericArguments 方法来获取 Int 类型。该方法动态的生成 Nullable 类型,并分配给 NullableType 字段。最后,传入整数构造一个新的 Nullable 类型的类。
String 范围
在 .NET Framework 中的 String 支持各种字符集和语言。这非常棒,因为 SQL Server 中支持不同语言和区域。虽然 .NET Framework 不需要验证 String 的长度,这意味着即使 SQL Server 里是 50 个字符长度的字段,在 .NET Framework 中的 String 也是无限的。
传入到数据库之前,我们要验证 String 长度是很重要的。在数据上这样的错误是很令人讨厌,并很难识别。因为通常返回到调用方的错误信息不是具体的错误信息,它没有告诉你哪个字段超出了长度的限制。
在 .NET UI Controls 中允许用户限定输入的 String 最大长度。它在服务器端进行检查;但是如果是硬编码一个字符串长度,会使得它变得难以维护。(因为对该数据库的任何更改影响在用户界面)
Date Type 范围
在 .NET Framework 中的 Date 类型支持 SQL Server 数据库,但是你要注意 Date 值的范围。例如,.NET Framework 支持的最小日期值是 1/1/0001,在 SQL Server 中并不支持这个值。
在 SQL Server 中,datetime 数据类型的最小值是 1/1/1753 ,并且 smalldatetime 数据类型的最小值是 1/1/1900。在最大值范围中,smalldatetime 是不适合用 DateTime.MaxValue 属性来设置的。
如果外部超出范围的 Date 被传入,一个 DateTime 溢出异常将被抛出,这显然是使用数据转换时的一个问题。此外,String 类型没有任何长度限制,不能很方便的捕获,除非手动验证。
数据输入
在 ASP.NET 中,尽管数据的类型不同,但通常是通过 textbox 控件输入的。一个文本框可以用于输入名字,数字,日期,其他类型。结合 AJAX control toolkit 一起使用,会很容易的限制文本框输入的字符类型。
当尝试从 textbox 中获取 Value 并转换成所需要的数据类型时,需要注意些。例如,当一个 DateTime 数据类型,输入时必须进行检查以确保是正确的。如果用户输入以下值:13/13/2008,1/113/2008,4/32/2008 ,当这些值被传递给 DateTime.Parse 或者 Convert.ToDateTime 进行转换时,会引发异常。
最好是使用 DateTime.TryParse 来转换日期值。如果日期值是无效的 TryParse 将返回一个空值,并且返回 false。数字类型的值也同样适用;至少使用 TryParse 转换,是一种防止出错的好方法。
理想情况下,最好在输入值的地方限制或阻止无效的输入。AJAX control toolkit 中包含了限制控件文本输入的扩展控件。例如,MaskedEditExtender 扩展控件可以限制必须以货币、日期、数字类型 input。
Listing 7
作为一种替代方案,FilteredTextBoxExtender 扩展控件可以限制 input 到文本框的字符类型。它可以控制 input 的只能是数字、小写字母、大写字母、自定义字符、任意组合。以下代码是筛选掉数字以外的字符,但是允许 input 一个小数点。
Listing 8
您同样也可以使用 Validation 控件。Validation 控件可以限制无效的 input 。例如,即使 FilteredTextBoxExtender 筛选掉了数字和小数点以外的值,他将允许多个小数点。下面的 RegularExpressionValidator 控件将阻止录入多个小数点。
Listing 9
ErrorMessage="Enter a valid dollar amount" ValidationExpression="\d+\.\d{2}" />
So plan your interface accordingly.
结论
数据转换看起来不是那么简单,最重要的是基础数据的类型。可以使用单个方法将值进行转换。也要在 UI 上取保您的正确数据。