将DataRow值转换为强类型值

本文关键字:强类型 转换 DataRow | 更新日期: 2023-09-27 18:21:18

我想将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;
    }

这种方法如预期的那样有效。我可以转换可为null的值、字符串和值类型。好的,如果有人输入类似于自己类型的废话,我需要捕捉异常。但除此之外,一切都很好。

但是,如果我加载一个包含30000行和20列的DataTable,并希望使用此方法转换每个值以创建对象,那么我的性能就非常不足。如果我对每个值使用int id = Convert.ToInt32(row["ID"]);等,它比我的一般方法快5-6倍。但我不喜欢手动转换它们,尤其是在有很多DBNull.Value的情况下。

我认为问题在于通过以下方式获取底层类型:

Type typ = Nullable.GetUnderlyingType(typeof(T));

这是reflex调用,并减慢我的方法。这是对的吗?有没有人遇到了同样的问题,并且可能有一个快速的解决方案?

更新:

我们的标准选择是这样工作的:

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 Id{get;set}
  public string Name{get;set}
}

在数据库(Oracle)中,我有一个表"Customer",它有两列Id number(5)Name varchar2(100)

现在我想读取所有的Customer并将它们转换为Customer对象。因此,我通过我们的标准例程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"](以及每个选择的所有其他值)是字符串。所以菲尔德不能投这个。我的ConvertTo可以做到这一点,但在大型表上性能严重不足。

将DataRow值转换为强类型值

你在这里做的事情很奇怪。您必须理解,C#中的所有数据类型都继承自对象。那么int,int?,char、double、class MyClass、struct MyStruct,它们都继承自对象。

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

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

这里的例外情况是,当数据是DBNUll时,必须在强制转换之前进行测试实际上不需要Convert类,因为它在大量使用反射,这是性能损失的来源

EDIT[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不等价,对象由int继承,即使不是直接继承。。。

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

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

这能满足您的要求吗?如果错误频繁,就会有人无意中听到他们,但如果不是,我不认为这是个问题。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 );
    }
}