如何安全地将DB值分配给对象属性?

时间:2009-04-10 13:58:17

标签: c# .net

我有一些(稍微多一点)代码,其中DataReader的对象值被分配给业务对象属性。使用大量的空合并,解析等来查看这段代码是非常不整洁的。我想知道如何实现一种更整洁的方法,将对象值从DataReader转换为对象属性的适当值。

一种选择是为每种数据类型提供离散转换函数,其中我的对象属性上的数据类型仅限于一小组CLR基元类型。我不使用可空类型,因为我只是将不整洁的情况转移到其他地方使用这些值的地方。

我的第二个选择是为所使用的四种左右数据类型中的每一种创建一个扩展方法,仅限于发生这些分配的利基命名空间。像这样相当简单的例子:

    public static void SafelyAssign(this string target, object value)
    {
        if (value is string)
        {
            target = (string)value ?? "";
        }
        else
        {
            target = value.ToString();
        }
    }

我还有其他选择吗?

8 个答案:

答案 0 :(得分:3)

我喜欢的解决方案是从CSLA.Net框架中采用的。创建一个实现IDataReader的SafeDataReader。我将尝试找到一个链接,但这里是一个如何获得安全int的示例。这个解决方案不需要CSLA.Net框架,这就是他如何解决我觉得非常方便的问题。

public class SafeDataReader : IDataReader, ISafeDataRecord
{
    /// <summary>
    /// Gets an integer from the datareader.
    /// </summary>
    /// <remarks>
    /// Returns 0 for null.
    /// </remarks>
    public int GetInt32(string name)
    {
        return GetInt32(_dataReader.GetOrdinal(name));
    }
    /// <summary>
    /// Gets an integer from the datareader.
    /// </summary>
    /// <remarks>
    /// Returns 0 for null.
    /// </remarks>
    public int GetInt32(int i)
    {
        if (_dataReader.IsDBNull(i))
            return 0;
        else
            return _dataReader.GetInt32(i);
    }

    public object this[string name]
    {
        get
        {
            object val = _dataReader[name];
            if (DBNull.Value.Equals(val))
                return null;
            else
                return val;
        }
    }
    public Nullable<T> GetNullable<T>(string name) where T:struct
    {
        int index = _dataReader.GetOrdinal(name);

        if (_dataReader.IsDBNull(index))
        {
            return null;
        }
        else
        {
            return new Nullable<T>((T)_dataReader[index]);
        }
    }
}

编辑:好的,我们如何使用这个安全的数据阅读器?所以我们假设我们有一个cmd对象,它都是设置

   using (SafeDataReader reader=new SafeDataReader(cmd.ExecuteDataReader())
   {
       //Assume MyColumn was null here, this will still work because 
       //since a string can be null 
       string myString=reader["MyColumn"];

       //In this case myInt will be set to 0 if MyColumn is DBNull
       int myInt=reader.GetInt("MyColumn");

   }

你也可以使用GetNullable方法,虽然这不是他班级的一部分,但我不确定它来自哪里,但它对我来说是正确的。

这有多便携?好吧,因为它正在实现IDataReader,它应该是非常便携的;只要调用代码不期望返回DBNull.Value,就应该能够进行交换。

答案 1 :(得分:2)

而不是重新发明轮子,我只是使用。

oReader.GetString(oReader.GetOrdinal("MyColumn"))

将获得您的特定类型的值。重要的是要注意,如果此过程的值为null,则仍然可能存在此问题。

答案 2 :(得分:1)

我更可能扩展SQLDataReader(Int32下面的例子)

// safe int32 assignment (using column name)
public static int GetInt32Safe(this SqlDataReader dr, string column)
{ 
    object value = dr[column];
    if (value != DBNull.Value)
    {
        return (int)value;
    }
    else
    {
        return 0;
    }
}

// safe int32 assignment (using column index)
public static int GetInt32Safe(this SqlDataReader dr, int colno)
{ 
    if (!dr.IsDBNull(colno))
    {
        return dr.GetInt32(colno);
    }
    else
    {
        return 0;
    }
}

答案 3 :(得分:1)

我更喜欢使用可空类型。以下方法可用于任何类型。您只需提供列名称。如果您没有使用Nullable类型,则可以删除IsNullable检查。

public static bool IsNullableType(Type valueType)
{
    return (valueType.IsGenericType && 
        valueType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
}

public static T GetValue<T>(this IDataReader reader, string columnName)
{
    object value = reader[columnName];
    Type valueType = typeof(T);
    if (value != DBNull.Value)
    {
        if (!IsNullableType(valueType))
        {
            return (T)Convert.ChangeType(value, valueType);
        }
        else
        {
            NullableConverter nc = new NullableConverter(valueType);
            return (T)Convert.ChangeType(value, nc.UnderlyingType);
        }
    }            
    return default(T);
}

答案 4 :(得分:0)

我选择了一条稍微不同的路线来解决这个问题。我选择创建一个类似于Parse()方法的通用静态库,以在任何对象上实现这一点。它开始是因为我在紧凑框架上,它没有TryParse()方法。这是一点点工作,我相信有更有效的方法;但它的效果非常好,并且我的同事已经很好地接受了。

此外,性能优于我的预期。我期望性能稍微松弛,因为我大量使用异常。其中一种方法的一个例子是:


public static class Parser{
  public static DateTime GetDateTime(object value){
    return GetDateTime(value, default(DateTime));
  }

  public static DateTime GetDateTime(object value, DateTime defaultValue){
    try{
      return DateTime.Parse(value.ToString());
    }
    catch{
      return defaultValue;
    }
  }
}

这允许我对任何事情进行调用;包括数据阅读器。


DateTime dateTime = Parser.GetDateTime(oReader("BirthDate"));

正如我所说,我最初的意图是在CF上实现TryParse()方法,但即使在桌面上也能很好地为我们做好准备。

答案 5 :(得分:0)

作为一个临时措施,我在我指定的五种基本类型中的每一种上创建了一个名为AssignObjectValue的扩展方法。这允许我在每个赋值上调用“相同”方法,而不管LHS类型如何。我本来希望能够为=运算符重载,但我现在很高兴。我的代码看起来像这样:

        cDoc.DocType.AssignObjectValue(dr["DocType"]);           // int
        cDoc.DocID.AssignObjectValue(dr["docID"]);
        cDoc.CustomerNo.AssignObjectValue(dr["customerNo"]);     // string
        cDoc.InvTitle.AssignObjectValue(dr["InvTitle"]);
        cDoc.TaxPercent.AssignObjectValue(dr["taxPercent"]);     // double
        cDoc.TaxTypeID = GetTaxTypeIDForDocType(cDoc.DocType);
        cDoc.Remarks.AssignObjectValue(dr["remarks"]);
        cDoc.Cost.AssignObjectValue(dr["cost"]);                 // double
        cDoc.InvDate.AssignObjectValue(dr["InvDate"]);           // date

这或多或少是我想成为的地方。我最终将基于映射字典在循环中进行分配,而不是在代码中明确地进行分配,所以我认为我的“半”解决方案对于投入临时修复的时间并不坏。

答案 6 :(得分:0)

作为SqlDataReader的扩展方法如何:

public static T GetValueNotNull<T>(this SqlDataReader reader, object checkValue)
    {
        T outValue;
        if (checkValue == DBNull.Value)
            outValue = default(T);
        else
            outValue = (T)checkValue;
        return outValue;
    }

而不是每个基本类型使用泛型,如果值为DBNull,则发回指定类型的默认值。我认为可以根据需要调整任何类型的DataReader而不是SqlDataReader。

答案 7 :(得分:0)

我的解决方案是:

private static T GetValue<T>(object o) {
    if (typeof(DBNull) != o.GetType()) {
        return (T) o;
    }
    return default(T);
}

何时,Status = GetValue<string>(currentDataRow["status"])

相关问题