我想将DataRow中的值转换为强类型变量。我想到了类似的东西:
int id = row.Field<int>("ID");
但问题是,我们的标准Db-Select返回DataTable ,其中每个值都是字符串的类型。意味着每个值都是对象,列没有特定的类型。由此Field<T>
抛出InvalidCastException,因为我尝试将对象强制转换为int。
所以我自己做了一些事情:
public static T ConvertTo<T>(this DataRow row, string columnName)
{
T returnValue = default(T);
Type typ = Nullable.GetUnderlyingType(typeof(T));
if ((row != null) &&
(row[columnName] != null) &&
(!Convert.IsDBNull(row[columnName])))
{
if (typ == null)
{
returnValue = (T)Convert.ChangeType(row[columnName], typeof(T));
}
else
{
returnValue = (T)Convert.ChangeType(row[columnName], typ);
}
}
return returnValue;
}
此方法按预期工作。我可以转换可空值,字符串和值类型。好吧,如果有人像自己的类型那样输入废话,我需要捕获异常。但是没关系。
但是,如果我加载一个包含30000行和20列的DataTable,并希望使用此方法转换每个值以进行对象创建,那么我将获得巨大的性能缺失。如果iam对每个值使用int id = Convert.ToInt32(row["ID"]);
等等,它比我的一般方法快5-6倍。但我不喜欢手动转换它们,特别是如果有很多DBNull.Value
。
我认为问题是通过以下方式获得基础类型:
Type typ = Nullable.GetUnderlyingType(typeof(T));
这是反射调用并减慢了我的方法。这是正确的吗?是否有人遇到同样的问题并且可能有快速解决方案?
更新
我们的标准选择工作原理如下:
DbDataAdapter dataAdapter = new DbDataAdapter();
dataAdapter.SelectCommand = cmd;
dataSet = new DataSet();
dataAdapter.Fill(dataSet);
dtReturn = dataSet.Tables[0].Copy();
DbDataAdapter internaly使用OracleDataAdapter。
更新
为了澄清,我做了以下(只是一个小例子):
我有一个表示选择查询的类。例如:
public class Customer
{
public int Id{get;set}
public string Name{get;set}
}
在数据库(Oracle)中,我有一个表“Customer”,它有2列Id number(5)
和Name varchar2(100)
。
现在我想阅读所有客户并将其转换为客户对象。 所以我通过标准的Routine SqlSelect读取数据。这会返回一个DataTable。此方法的实习生在第一次更新之前发布。
现在我遍历此DataTable中的每个DataRow并转换此Cell值以进行对象创建。
List<Customer> myList = new List<Customer>();
foreach (DataRow row in SqlSelect())
{
Customer customer = new Customer();
customer.Id = Convert.ToInt32(row["ID"]);
customer.Name = Convert.ToString(row["NAME"]);
myList.Add(customer);
}
这样就可以了。但我希望转换成:
customer.Id = row.ConvertTo<int>("ID");
如果我在行[“ID”]的DataTable为int,则Field Method可以很好地处理这个问题。特别是如果有其他列可以为null等。但在我的情况下,行[“ID”](以及每个选择的所有其他值)是字符串。所以Field不能投这个。我的ConvertTo可以做到这一点但是在大型桌子上缺乏巨大的性能。
答案 0 :(得分:3)
你在这里做的很奇怪。您必须了解,C#中的所有数据类型都从对象继承。所以int,int?,char,double,类MyClass,struct MyStruct,它们都从对象继承。
因此row [columnName]包含int类型的数据,那么即使它是返回对象,也可以直接转换为int。
int i = (int)row[columnName]; //when int data,
这里的例外是,当数据是DBNUll时,必须在转换之前进行测试。 确实没有必要使用Convert类,因为它正在大量反映性能损失的来源!
编辑[1]:@Scott张伯伦是对的,我改进了代码,使其对价值类型更安全:
public static class IsNullable<T>
{
private static readonly Type type = typeof(T);
private static readonly bool is_nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
public static bool Result { get { return is_nullable; } }
}
public static class Extension
{
public static T CastColumnData<T>(this DataRow row,
string columnName)
{
object obj;
if (row == null) throw new ArgumentNullException("row is null");
if ((obj = row[columnName]) == null) throw new ArgumentNullException("row[" + columnName + "] is null");
bool is_dbnull = obj == DBNull.Value;
if (is_dbnull && !IsNullable<T>.Result) throw new InvalidCastException("Columns data are DbNull, but the T[" + typeof(T).ToString() + "] is non nullable value type");
return is_dbnull ? default(T) : (T)obj;
}
}
类转换在这里用于其他东西,用于在两种数据类型之间创建相同的值,这些数据类型共享一些共同点,但在这里使用大量反射非常重要。
object和int不等价,对象是由int继承的,即使不直接...
EDIT [2]:对原始问题的新更新最终澄清,所有列类型实际上都是字符串,而不是不同类型的数据类型,并且必须将它们转换/解析为可请求的数据类型。强>
我担心没有更快的解决方案,因为值类型没有从泛型类型的接口实现Parse和TryParse。它们是静态方法,这使得通过泛型解决这个问题的尝试很成问题。
答案 1 :(得分:0)
这样做你想要的吗?如果错误频繁发生,那么就会听到它们,但如果不是,我也不会发现这是一个问题。 Convert.ChangeType可能在场景下使用反射,因此也可能有点慢......
public static T ConvertTo<T>( this DataRow dataRow, string columnName )
{
var defaultValue = default( T );
var valueOfCell = GetCellValue(dataRow, columnName);
if ( defaultValue == null && valueOfCell == null )
{
return default( T );
}
try
{
return ( T )Convert.ChangeType( valueOfCell, typeof( T ) );
}
catch ( InvalidCastException ex )
{
return default( T );
}
}