65.9K
CodeProject 正在变化。 阅读更多。
Home

SqlTypes 的数据绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (19投票s)

2003年4月24日

2分钟阅读

viewsIcon

225817

downloadIcon

3704

使用 PropertyDescriptor 类和 ITypedList 接口进行 SqlTypes 的数据绑定

引言

假设我们有一个对象,它封装了数据库中的一些数据。最终,我们希望将它们的集合显示在 DataGrid 或其他可绑定的组件中。为了便于讨论,我们将有一个类来封装一个 DataRow,以及封装其单元格的属性。

public class DataRowWrapper
{
  private DataRow dataRow;

  public DataRowWrapper(DataRow dr)
  {
    this.dataRow = dr;
  }

  public int ID
  {
    get { return (int) this.dataRow["ID"]; }
  }

  public DateTime dtStamp
  {
    get { return (DateTime) this.dataRow["dtStamp"]; }
    set { this.dataRow["dtStamp"] = value; }
  }
}

问题

这个例子看起来很好,但不幸的是,一个单元格可能是空的,而 int 或 DateTime 不能为 null! 这就是问题所在。这段代码会在任何空数据上抛出异常,并且我们无法将 null 值赋给这些值。因此,我们可以使用 SqlTypes,它允许 null 值。

public SqlDateTime dtStamp
{
  get
  {
    if (this.dataRow.IsNull("dtStamp"))
      return SqlDateTime.Null;
    else
      return new SqlDateTime((DateTime)this.dataRow["dtStamp"]);
  }
  set
  {
    if ( value.IsNull )
      this.dataRow["dtStamp"] = DBNull.Value;
    else
      this.dataRow["dtStamp"] = value.Value;
  }
}

现在我们已经破坏了数据绑定。数据绑定不适用于 SqlTypes。SqlTypes 不可编辑。我认为这是一个很大的疏忽,但有一个解决方案 - PropertyDescriptors(属性描述符)。

解决方案

属性描述符允许我们仅通过数据绑定公开 SqlTypes 的底层类型,并为 object 的正常使用保留所有其他内容。属性描述符的一个问题是,我们需要实现 ITypedList 才能使其有用。ITypedList 包含方法签名 GetItemProperties(),我们可以用它来返回我们自己的属性描述符。我们可以为每个 SqlTypes 编写一个属性描述符,但这里有一个更简单的方法。

public class SqlPropertyDescriptor : PropertyDescriptor
{
  public static SqlPropertyDescriptor GetProperty(string name, Type sqlType)
  {
    Type baseType = sqlType.GetProperty("Value").PropertyType;
    ArrayList attribs = new ArrayList(TypeDescriptor.GetAttributes(baseType));
    Attribute[] attrs = (Attribute[])attribs.ToArray(typeof(Attribute));
    return new SqlPropertyDescriptor(name,attrs,sqlType,baseType);
  }

  private Type SqlType;
  private Type BaseType;

  protected SqlPropertyDescriptor( string name,Attribute[] attrs,
    Type sqlType, Type baseType ) : base(name,attrs)
  {
    SqlType = sqlType;
    BaseType = baseType;
  }

  ...

  public override void SetValue(object component,object value)
  {
    PropertyInfo pi = component.GetType().GetProperty(this.Name);
    Object o;
    if ( value == DBNull.Value )
    {
      o = component.GetType().GetField("Null", BindingFlags.Static
        | BindingFlags.Public | BindingFlags.GetField).GetValue(component);
    }
    else
    {
      o = pi.PropertyType.GetConstructor(new Type[]{BaseType}).Invoke(
        new Object[]{value});
    }
    pi.SetValue(component,o, null);
  }

  public override object GetValue(object component)
  {
    object Property = component.GetType().GetProperty(this.Name).GetValue(
      component,null);

    if ( (bool)Property.GetType().GetProperty("IsNull").GetValue(Property,
        null) )
      return DBNull.Value;

    return Property.GetType().GetProperty("Value").GetValue(Property,null);
  }
}

在这里我们可以看到,我重写了 SetValue()GetValue(),并且由于它们传递了数据类型 object,因此可以处理 null 值。我使用一些反射来实现所需的结果。SetValue() 可以将 null 值放入属性中,并且 GetValue() 可以在需要时返回 null 值。要使用属性描述符,我们必须在集合中实现 ITypedList,并为所有是 SqlTypes 的属性返回我们自定义的属性描述符。在我的代码中,我调用以下方法来替换属性描述符。

protected PropertyDescriptorCollection GetPropertyDescriptorCollection(
  ArrayList properties )
{
  if ( properties == null || properties.Count == 0 )
    return new PropertyDescriptorCollection(null);

  ArrayList output = new ArrayList();

  foreach ( PropertyDescriptor p in properties )
  {
    if ( p.Attributes.Matches(new Attribute[]{
      new BindableAttribute(false)}) ) continue;

    if ( p.PropertyType.Namespace == "System.Data.SqlTypes" )
    {
      // create the base type property descriptor
      output.Add(SqlPropertyDescriptor.GetProperty(
        p.Name, p.PropertyType ) );
    }
    else
    {
      output.Add(p);
    }
  }
  return new PropertyDescriptorCollection(
    (PropertyDescriptor[])output.ToArray(typeof(PropertyDescriptor)));
}

因此,现在我们有一个属性是数据库和 null 友好的类,我们可以在网格中显示它并编辑它,并且编写的方法足够通用,可以在所有 SqlTypes 上使用。

© . All rights reserved.