SqlTypes 的数据绑定






4.70/5 (19投票s)
2003年4月24日
2分钟阅读

225817

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
上使用。