首先,让我解释一下当前的情况:我正在从数据库中读取记录并将它们放在一个对象中供以后使用;今天出现了关于数据库类型到C#类型转换(cast?)的问题。
让我们看一个例子:
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?).
编译器错误。
要解决此问题,我目前知道两种方式(让我们使用field_b
示例):第一个是this.field_b = (Byte) reader["field_b"];
,第二个是this.field_b = Convert.ToByte(reader["field_b"]);
。< / p>
第一个选项的问题是DBNull
字段在强制转换失败时抛出异常(即使可归类型为String
),第二个问题是它不保留null值(Convert.ToString(DBNull)
产生String.Empty
),我也不能将它们与枚举一起使用。
因此,在互联网和StackOverflow上进行了几次查询之后,我想出的是:
public static class Utilities
{
public static T FromDatabase<T>(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);
}
}
}
这样我应该处理每一个案例。
问题是:我错过了什么吗?我做了一个女人(浪费金钱,大脑和时间)因为有更快更清洁的方法吗?一切都正确吗?利润是多少?
答案 0 :(得分:38)
如果字段允许空值,请不要使用常规基元类型。使用C# nullable
type和as
keyword。
int? field_a = reader["field_a"] as int?;
string field_b = reader["field_a"] as string;
将?
添加到任何不可为空的C#类型使其“可以为空”。使用as
关键字将尝试将对象强制转换为指定的类型。如果强制转换失败(就像类型为DBNull
那样),则运算符返回null
。
注意:使用as
的另一个好处是它比正常投射slightly faster。由于它也可能有一些缺点,例如如果你试图将错误类型转换为更难跟踪错误,这不应被视为始终使用as
而不是传统投射的原因。常规铸造已经是相当便宜的操作。
答案 1 :(得分:12)
您不想使用reader.Get*
methods吗?唯一令人烦恼的是他们采用列号,因此您必须在调用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
}
答案 2 :(得分:6)
这就是我过去处理它的方式:
public Nullable<T> GetNullableField<T>(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<int>(reader.GetOrdinal("myIntField"));
答案 3 :(得分:4)
您可以制作一组扩展方法,每种数据类型一对:
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<T>
method的DataSet的相同问题,该问题通常处理多种数据类型,并且可以将DBNull转换为Nullable。
作为一个实验,我曾经为DataReaders实现了一个等效的方法,但我最终使用Reflector从DataSetExtensions(UnboxT)借用一个内部类来有效地进行实际的类型转换。我不确定分发借用类的合法性,所以我可能不应该共享代码,但是很容易查找自己。
答案 4 :(得分:2)
这里发布的通用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);
}