如何(有效地)将SqlDataReader字段转换(强制转换)为其对应的c#类型?

首先,让我解释一下当前的情况:我正在从数据库中读取记录并将它们放在一个对象中供以后使用; 今天出现了一个关于C#类型转换(转换?)的数据库类型的问题。

我们来看一个例子:

namespace Test { using System; using System.Data; using System.Data.SqlClient; public enum MyEnum { FirstValue = 1, SecondValue = 2 } public class MyObject { private String field_a; private Byte field_b; private MyEnum field_c; public MyObject(Int32 object_id) { using (SqlConnection connection = new SqlConnection("connection_string")) { connection.Open(); using (SqlCommand command = connection.CreateCommand()) { command.CommandText = "sql_query"; using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow)) { reader.Read(); this.field_a = reader["field_a"]; this.field_b = reader["field_b"]; this.field_c = reader["field_c"]; } } } } } } 

这显然是失败的,因为三个this.field_x = reader["field_x"]; 调用抛出Cannot implicitly convert type 'object' to 'xxx'. An explicit conversion exists (are you missing a cast?). Cannot implicitly convert type 'object' to 'xxx'. An explicit conversion exists (are you missing a cast?). 编译器错误。

为了纠正这个问题,我目前知道两种方式(让我们使用field_b示例):第一个是this.field_b = (Byte) reader["field_b"]; 第二个是this.field_b = Convert.ToByte(reader["field_b"]);

第一个选项的问题是DBNull字段在强制转换失败时抛出exception(即使可空类型为String ),第二个问题是它不保留空值( Convert.ToString(DBNull)产生一个String.Empty ),我也不能在枚举中使用它们。

因此,在互联网和StackOverflow上进行了几次查询之后,我想出的是:

 public static class Utilities { public static T FromDatabase(Object value) where T: IConvertible { if (typeof(T).IsEnum == false) { if (value == null || Convert.IsDBNull(value) == true) { return default(T); } else { return (T) Convert.ChangeType(value, typeof(T)); } } else { if (Enum.IsDefined(typeof(T), value) == false) { throw new ArgumentOutOfRangeException(); } return (T) Enum.ToObject(typeof(T), value); } } } 

这样我就应该处理每一个案子。

问题是:我错过了什么吗? 我在做一个WOMBAT( 浪费金钱,大脑和时间 ),因为有更快更清洁的方法吗? 一切都正确吗? 利润?

如果字段允许空值,请不要使用常规基元类型。 使用C# nullable类型和as关键字 。

 int? field_a = reader["field_a"] as int?; string field_b = reader["field_a"] as string; 

添加? 任何不可为空的C#类型使其“可以为空”。 使用as关键字将尝试将对象强制转换为指定的类型。 如果转换失败(就像类型是DBNull ),那么运算符返回null

注意:使用as另一个小好处是它比正常铸造稍快 。 因为它也有一些缺点,例如如果你试图将错误类型转换为更难跟踪错误,这不应被视为总是使用传统铸造的原因。 常规铸造已经是相当便宜的操作。

你不想使用reader.Get*方法 ? 唯一令人烦恼的是它们采用列号,所以你必须在调用GetOrdinal()时包装访问器

 using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow)) { reader.Read(); this.field_a = reader.GetString(reader.GetOrdinal("field_a")); this.field_a = reader.GetDouble(reader.GetOrdinal("field_b")); //etc } 

这就是我过去处理它的方式:

  public Nullable GetNullableField(this SqlDataReader reader, Int32 ordinal) where T : struct { var item = reader[ordinal]; if (item == null) { return null; } if (item == DBNull.Value) { return null; } try { return (T)item; } catch (InvalidCastException ice) { throw new InvalidCastException("Data type of Database field does not match the IndexEntry type.", ice); } } 

用法:

 int? myInt = reader.GetNullableField(reader.GetOrdinal("myIntField")); 

您可以创建一组扩展方法,每种数据类型一对:

  public static int? GetNullableInt32(this IDataRecord dr, string fieldName) { return GetNullableInt32(dr, dr.GetOrdinal(fieldName)); } public static int? GetNullableInt32(this IDataRecord dr, int ordinal) { return dr.IsDBNull(ordinal) ? null : (int?)dr.GetInt32(ordinal); } 

这实现起来有点乏味,但它非常有效。 在System.Data.DataSetExtensions.dll中,Microsoft使用Field方法解决了DataSets的相同问题,该方法通常处理多种数据类型,并且可以将DBNull转换为Nullable。

作为一个实验,我曾经为DataReaders实现了一个等效的方法,但我最终使用Reflector从DataSetExtensions(UnboxT)借用一个内部类来有效地进行实际的类型转换。 我不确定分发借用类的合法性,所以我可能不应该共享代码,但是查找自己很容易。

这里发布的通用hanlding代码很酷,但由于问题标题包含“有效”这个词,我将发布我的不那么通用但(我希望)更有效的答案。

我建议你使用别人提到的getXXX方法。 为了处理bebop谈到的列号问题,我使用枚举,如下所示:

 enum ReaderFields { Id, Name, PhoneNumber, ... } int id = sqlDataReader.getInt32((int)readerFields.Id) 

这是一个额外的输入,但是你不需要调用GetOrdinal来查找每列的索引。 并且,您不必担心列名称,而是担心列位置。

要处理可为空的列,您需要检查DBNull,并可能提供默认值:

 string phoneNumber; if (Convert.IsDBNull(sqlDataReader[(int)readerFields.PhoneNumber]) { phoneNumber = string.Empty; } else { phoneNumber = sqlDataReader.getString((int)readerFields.PhoneNumber); }