Click here to Skip to main content
Click here to Skip to main content

Databinding the SqlTypes

By , 1 May 2003
 

Introduction

Lets say we have an object, which wraps some data from the database. Eventually we want a collection of them displayed in a DataGrid or some other bindable component. For the purpose of this discussion, we will have a class that wraps a DataRow, and properties that wrap its cells.

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; }
  }
}

The Problem

This example may look all well and good, but unfortunately a cell can be null, and an int or DateTime cannot! Herein lies the problem. This code will throw exceptions on any null data, and we cannot assign null to the values. So we can use SqlTypes which allow null values.

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;
  }
}

So now we have ruined it for data binding. Data binding does not work for SqlTypes. SqlTypes are not editable. I see this as a big oversight, but there is a solution - PropertyDescriptors.

The Solution

Property descriptors allow us to expose the SqlTypes underlying type ONLY through data binding, and leave everything else the same for normal use of the object. One problem with property descriptors is that we need to implement ITypedList for it to be any use. ITypedList contains the method signature GetItemProperties(), which we can return our own property descriptors with. We could write one property descriptor for each of the SqlTypes, but here’s an easier way.

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

Here we can see that I have overridden the SetValue() and GetValue(), and since they pass around the data type object, they can handle nulls. I use some reflection to achieve the desired result. SetValue() can put null into the property, and GetValue() can return a null if need be. To use the property descriptors, we must implement ITypedList in our collection, and return our custom property descriptor for all the properties where they are SqlTypes. In my code, I call the following method to replace the property descriptors.

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

So, now we have a class with properties that are database and null friendly, we can show it in a grid and edit it, and the methods written are generic enough that we can use then on all the SqlTypes.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Dan Glass
Architect support.com
Australia Australia
Member

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralBinding data in database using a Classmembermadhan singh raja26 Mar '06 - 23:59 
Smile | :)
Normally in web application we wil bind the data into the datagrid in the code behind, but i want it to be generalized and so that if we write it as a class file in the root folder and inherit it where ever we are in need, i want to know how to inherit the class in the code behind of our form..
 
with regards,
madhan
 
M. Madhan Singh Raja., M. Sc.,
System Engineer,
E*Pro Tech,
Chennai.
GeneralVery nice but!!!memberBrandon Parker14 Dec '04 - 22:46 
Microsoft is hot on your trail...
http://msdn.microsoft.com/vcsharp/2005/overview/language/nullabletypes/[^]
QuestionAnyone done this in a web app?memberzsoltvotin19 May '04 - 4:34 
The code seems to work great in a Windows app. The GetValue() and the SetValue() methods in the SqlPropertyDescriptor are called when binding to a datagrid.
 
However, when implementing SqlPropertyDescriptor in a web app, the GetValue() and the SetValue() methods are never called. It seems the GetValue() and SetValue() methods on the base class (PropertyDescriptor) are called instead.
 
Anyone have any suggestions?
AnswerRe: Anyone done this in a web app?memberibrahim rodriguez29 Jun '05 - 14:59 
I'm having issues getting this to work for web apps also. Makes me really double guess myself for having used custom business objects instead of datasets....
QuestionHow did you figure it out?memberjjreilly26 Sep '03 - 1:29 
Dan,
 
Great article. I wish I'd seen it before I worked so hard trying to figure out the same stuff. Which brings me to my question:
 
How did you figure out complex data binding, ITypedList, etc? Is there a secret uSoft resource, or just the usual hard work of banging head, searching CodeProject, and google?
 
It seems that understanding the ComponentModel namespace is a good thing, but there should be a better way other than the hard way.
 
Thanks for any help.
 
john.
AnswerRe: How did you figure it out?member小康3 Apr '04 - 1:14 
yes,could you give us the whole source code?
 
thanks very much
 
Just Studying Thinking Loving
GeneralRe: How did you figure it out?memberDan Glass4 Apr '04 - 13:11 
That is the whole source.
What are you missing?
 
Dan Glass
Olero Software
ORM.NET Data Access made easy
 
Personal site:
www.danglass.com
GeneralSetting Values to Null Using BindingmemberSharZoe5 Jun '03 - 6:04 
Very nice Dan
 
But.... Using your example, try to "null out" a date. For some reason, the Set accessor isn't even being called! I've set up a bigger example. Using it, I've discovered that if I try to set the value of any SqlType to "empty" or null, or even the wrong type (i.e. type a string into a date column), the Set accessor is called!
 
I have coded the Set accessors to convert values like these properly but I can't use them if they don't get called. I'm trying to create my own collections, no DataSets/DataTables. And have done so with great success. Except I can't display null standard DataBindings. What's displayed is a default value (i.e. zero for ints). Your example comes very close to fixing this. Any ideas on how to "finish" it?
 
Thanks
SharZoe
GeneralApplying it to a single business objectmemberPaulo4 May '03 - 12:53 
Very good article! Smile | :)
But I have one question... What about applying this technique to a single business object so I can bind this object properties (which are SqlTypes) to several TextBoxes and Checkboxes (and so on...) in the form ?
Certainly I wouldn't derive the business object from the ITypedList interface... That would apply to a collection!
Thanks in advance
 
Paulo
GeneralRe: Applying it to a single business objectmemberrahtrow4 May '03 - 16:15 
great article, I am looking for a way to do the same...

GeneralRe: Applying it to a single business objectmemberDBuckner6 May '03 - 17:24 
I agree, thanks Dan!! I am also wondering on the part of Paulo and have been for some time now. Anyone have any insight on this?
 
Thanks,
Dave
GeneralRe: Applying it to a single business objectmemberDan Glass7 May '03 - 7:33 
The business object will need to implement the ICustomTypeDescriptor interface.
A PropertyDescriptor will need to be written too. Here is mine:
 

// biz object
 
#region ICustomTypeDescriptor explicit interface definitions
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
return GetPropertyDescriptorCollection(attributes);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
 

private PropertyDescriptorCollection GetPropertyDescriptorCollection( Attribute[] attributes )
{
ArrayList output = new ArrayList();
 
foreach ( PropertyInfo pi in this.GetType().GetProperties() )
{
object[] atts = pi.GetCustomAttributes(typeof(BindableAttribute),true);
if ( atts.Length == 1 && atts[0] as BindableAttribute != null && ((BindableAttribute)atts[0]).Bindable == true ) continue;
 
if ( pi.PropertyType.Namespace == "System.Data.SqlTypes" )
{
// create the base type property descriptor
output.Add(OrmLib.SqlPropertyDescriptor.GetProperty( pi.Name, pi.PropertyType ) );
}
else
{
output.Add(GenericPropertyDescriptor.GetProperty(pi));
}
}
return new PropertyDescriptorCollection((PropertyDescriptor[])output.ToArray(typeof(PropertyDescriptor)));
}
 
/// end of biz object
 
internal class GenericPropertyDescriptor : PropertyDescriptor
{
///
/// Use this entry point
///

/// the property to make the PropertyDescriptor for
///
internal static GenericPropertyDescriptor GetProperty(PropertyInfo pi)
{
ArrayList attributes = new ArrayList();
 
foreach ( object o in pi.GetCustomAttributes(true) )
{
if ( o as Attribute != null ) attributes.Add(o);
}
 
return new GenericPropertyDescriptor(pi.DeclaringType, pi.PropertyType, pi.Name, (Attribute[])attributes.ToArray(typeof(Attribute)));
}
 
private Type componentType;
private Type propertyType;
 
protected GenericPropertyDescriptor(Type componentType, Type propertyType, string name, Attribute[] attrs) :base(name, attrs)
{
this.componentType = componentType;
this.propertyType = propertyType;
}
 
public override Type ComponentType
{
get { return componentType; }
}
 
public override bool IsReadOnly
{
get { return (Attributes.Matches(ReadOnlyAttribute.Yes)); }
}
 
public override Type PropertyType
{
get { return propertyType; }
}
 
public override bool CanResetValue(object component)
{
return false;
}
 
public override object GetValue(object component)
{
return componentType.GetProperty(this.Name).GetValue(component,null);
}
 
public override void ResetValue(object component)
{
throw new NotSupportedException();
}
 
public override void SetValue(object component, object value)
{
componentType.GetProperty(this.Name).SetValue(component,value,null);
}
 
public override bool ShouldSerializeValue(object component)
{
return true;
}
}
 
Dan Glass
www.olero.com
ORM.NET Data Access made easy
 
Personal site:
www.danglass.com
GeneralRe: Applying it to a single business objectmemberPaulo13 May '03 - 10:42 
Smile | :) Thank you Dan but there's a problem with databinding here when implementing the business object like that. The properties surely suceede in getting their values updated using native values but there's a problem of synchronization in the databinding of the form.
I'll give you an example:
Try to add two SqlType properties to a business object (eg. 'SqlString FirstName' and 'SqlString LastName') and a third property declared as a native type (eg. 'string FullName') which is the concatenation of the first two. If you had 3 text boxes to a form, each one representing one of the properties, you'll notice that the third is not automatically updating itself as I change one the first two!!!
If all these properties were of native types the databinding automatically synchronized the properties but ... that is not happening here when we change the properties to native type!! Eek! | :eek:
What I found out is that both derivations of PropertyDescriptor(GenericPropertyDescriptor and the SqlPropertyDescriptor) are somehow imcomplete because they are preventing the databinding of doing the synchronization stuff. When I change the code to not use the GenericPropertyDescriptor and use the original PropertyDescriptor the synchro of databinding suddently starts to work again!
Does anyone have a solution ? Frown | :(

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 2 May 2003
Article Copyright 2003 by Dan Glass
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid