将 DataRow 值转换为强类型值

Posted

技术标签:

【中文标题】将 DataRow 值转换为强类型值【英文标题】:Convert DataRow value to strongly typed value 【发布时间】:2015-11-10 14:41:41 【问题描述】:

我想将 DataRow 中的值转换为强类型变量。我想到了类似的东西:

int id = row.Field<int>("ID");

但问题是,我们的标准 Db-Select 返回一个 DataTable 其中每个值都是字符串类型。意味着每个值都是对象,列没有特定类型。通过这个Field&lt;T&gt; 抛出 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,并希望使用此方法来转换每个值以创建对象,我会遇到巨大的性能缺陷。如果我对每个值使用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 内部使用 OracleDataAdapter。

更新:

为了澄清,我正在做以下事情(只是一个小例子):

我有一个代表选择查询的类。例如:

public class Customer

  public int Idget;set
  public string Nameget;set

在数据库 (Oracle) 中,我有一个“客户”表,其中有 2 列 Id number(5)Name varchar2(100)

现在我想阅读所有客户并将它们转换为客户对象。 所以我通过我们的标准例程 SqlSelect 读取数据。这返回给我一个数据表。这个方法的实习生是在第一次UPDATE之前贴出来的。

现在我遍历此 DataTable 中的每个 DataRow 并转换此单元格值以创建对象。

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");

如果我在 row["ID"] 处的 DataTable 是 int 字段方法可以很好地处理这个问题。特别是如果还有其他列可以为空等。但在我的情况下,行[“ID”](以及每个选择的所有其他值)是字符串。所以菲尔德不能施放这个。我的 ConvertTo 可以做到,但在大型表上性能欠佳。

【问题讨论】:

使用分析器。为什么我们会知道您的 CPU 将时间花在哪里?此外,使用Convert 也有没有的意义。该值要么是DBNull.Value,要么是T 类型——只需进行简单的转换即可。 您的“标准数据库选择返回无类型数据表”?这怎么可能?你怎么填那张桌子?通常DataAdapter 从数据读取器中检索模式信息。 “我不喜欢手动转换它们,特别是如果有很多 DBNull.ValueDBNull.Value 有什么问题? DataRow.Field 支持可为空的类型。 @TimSchmelter 如果我可以使用 Field 方法,我不会对 DBNull.Value 有任何问题。如果我用 Convert.ToInt32() 等转换每个值,那就太划算了。但是 Field 方法不能像上面解释的那样工作。 @Sebi:为什么在返回之前复制整个表?如果您不需要填写的DataSet 用于其他目的,您可以return dataSet.Tables[0] @TimSchmelter 感谢您的提示。我没有开发这个程序。这是我们的标准,由一位同事制定。我会问他... 【参考方案1】:

你在这里做的很奇怪。您必须了解,C# 中的所有数据类型都继承自对象。所以int、int?、char、double、class MyClass、struct MyStruct,都是从object继承而来的。

所以 row[columnName] 包含 int 类型的数据,那么即使是返回对象,也可以直接转换为 int。

int i = (int)row[columnName]; //when int data,

这里的例外是,当数据为 DBNUll 时,必须在转换前进行测试。 确实不需要 Convert 类,因为它正在大量使用反射,这是性能损失的根源!

编辑[1]:@Scott Chamberlain 是对的,我改进了代码以使其对值类型更安全:

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;
     
 

Class Convert 用于其他目的,用于在两种数据类型之间创建等效值,它们共享一些共同点,但在这里它是使用大量反射的相当繁重的东西。

object 和 int 不等价,object 是由 int 继承的,即使不是直接...

编辑[2]:对原始问题的新更新最终澄清,所有列类型实际上都是字符串,而不是不同类型的数据类型,它们必须被转换/解析为请求的数据类型。强>

恐怕没有更快的解决方案,因为值类型不会从具有泛型类型的接口实现 Parse 和 TryParse。它们是静态方法,这使得通过泛型解决这个问题的尝试非常有问题。

【讨论】:

对于 int 之类的返回 default(T),我总是很谨慎,我不喜欢没有办法区分 0 和 DbNull。如果该类不可为空,则抛出异常通常是更安全的选择。您的代码适用于可空类型(Nullable&lt;T&gt; 结构和类),只是不可为空的类型我不信任它。 @ipavlu 很抱歉,存储在 DataTable 中的值不是对象。它们都是字符串。我在我的问题中纠正了这一点。 Field 方法无法将字符串转换为 int。该方法期望 DataColumn 也来自 int 类型。对象的内在影响是很清楚的。 @Sebi:如果您只在数据库中存储字符串,它们只是字符串。那么你有比性能更大的问题。 @TimSchmelter 数据库中不仅有字符串:D 这确实很恐怖。但是如果我在 DataTable 的单元格上使用这个 GetType() 我总是得到字符串。我想我需要研究我们的数据库方法... @Sebi:也许选择将其转换为varchar,或者您省略了有人使用row["column].ToString()而不是使用row.Field&lt;int&gt;("column")的代码。【参考方案2】:

这是你想要的吗?如果错误经常发生,就会有人无意中听到它们,但如果不是,我认为这不是问题。 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 );
    

【讨论】:

以上是关于将 DataRow 值转换为强类型值的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 foreach 的情况下将 ArrayList 转换为强类型泛型列表?

获取DataGridView上选中的一行并转换为一个DataRow类型

如何使用 StreamBuilder 消除颤振数据表错误“预期类型为 'List<DataRow>',但得到类型为 'List<dynamic>”的值之一?

java语法

如何在 C# 中将 List<DataRow> 转换为 List<T>?

如何在 DataGridView 中获取选定的 DataRow?