PropertyDescriptors and null values?

Posts   
 
    
Posts: 65
Joined: 07-Dec-2005
# Posted on: 15-Dec-2005 20:16:12   

Franz,

My boss and have been trying to hash out our problem of binding entity collections to a grid and having stronly typed properties. Since they're strongly typed (and not of type object like the ADO objects), reserved values are used when the actual data is null (you know this, of course).

This is not a problem for programmatic access of the properties, since we have the two null test functions, but binding them to a grid (of any sort) poses a problem, since the reserved value is displayed. In some cases (such as the one we're in now), that value is a valid value for that column. In other cases, ANY value is valid for a given column, so no value can be legitimately reserved.

However, one thing that came to me yesterday was the fact that databinding, either to a collection-listing control or to a single value control, doesn't actually (or doesn't have to) call a property directly or at all, since they all go through your PropertyDescriptor subclass. Given that, he (the descriptor) can return whatever value he wants, either the value for the column, or something to represent null (like DBNull.Value). What would be the feasibility of integrating an option that indicates to the PropertyDescriptor to return an actual null value instead of a reserved value? This would still retain type-safety (and ease of use) on the properties, while giving the necessary flexability in data binding.

How does that sound?

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 16-Dec-2005 07:41:45   

standard data types can't have a null value, except the string data type so generated properties descriptors can't return null values for those types.

Not until the Nullable DataTypes of dotNet 2.

So you may want to implement it manualy somehow:

You may want to create your own Entity Custom Properties, which will return the values you want, and use these in your databindings. Or may be by avoiding nulls in the first place simple_smile (which would be a big design switch)

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 16-Dec-2005 10:59:10   

Additionally, in v2.0 we'll introduce EntityView (working name) objects, which are objects similar to dataview objects, and which will form the connection between control and collection.

By creating derived classes from that class, you will be able to produce your own property descriptors if you want to. You can do that also today, but it's a little more complicated. In EntityCollection today, you can inject own code through an include template (see 'adding your own code to the generated code' in the documentation) which overrides GetItemProperties, the ITypedList method which produces the property descriptors for databinding. You can, there, replace property descriptors with your own.

However, your reasoning made one mistake: property descriptors are only used to control column behavior for grids/other controls with column data. Every value is read directly from the property, at least in some grids. Other grids will play nice and use the property descriptor, the object holding the property and fetch the data through the descriptor. So while your idea is good, it might not work in practise because the grid you're using does a direct read on the property itself. If it does use the property descriptor, you indeed can control what's shown by adding your own propertydescriptor object for that property. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Posts: 65
Joined: 07-Dec-2005
# Posted on: 16-Dec-2005 15:26:18   

I have to question that last portion of your post because of one issue: if that is true, then they could never bind to a DataView. The actual objects in a DataView are DataRowView objects, which do not have explicitly declared properties for each column, so reflection alone would fail to find a property for any column, and the grid wouldn't display any data. I fail to see a way that you could bind to something generic like the DataView without playing nice with the PropertyDescriptor paradigm.

Posts: 65
Joined: 07-Dec-2005
# Posted on: 16-Dec-2005 15:46:58   

Walaa wrote:

standard data types can't have a null value, except the string data type so generated properties descriptors can't return null values for those types.

Not until the Nullable DataTypes of dotNet 2.

So you may want to implement it manualy somehow:

You may want to create your own Entity Custom Properties, which will return the values you want, and use these in your databindings. Or may be by avoiding nulls in the first place simple_smile (which would be a big design switch)

You are correct, the actual property cannot return a null value for, say, an int.

However, databinding (as I've always understood it, and I don't see how it could function correctly any differently) uses System.ComponentModel.PropertyDescriptor implementations to provide the list of properties (which, in an ADO.NET DataView, are just columns on the table). These implementations essentially act as pass-thru's to the original object, accessing either a property or an indexed column in an abstracted way.

The rub is the fact that these descriptors do not HAVE to function in this manner. The descriptor has a function called GetValue and you pass it an object (an ADO.NET DataRowView, for example) and it returns an object. Because of this, a PropertyDescriptor could actually return a value that is DIFFERENT from that of simply calling the property. Something like this could go in the GetValue...

 if the column I'm looking at is null and I'm supposed to return DBNull.Value, return DBNull.value
else if the column I'm looking at is null, return the reserved value
else return the column value

Obviously, that is a greatly simplified version of what would be written, but it's the general idea that's relevant. If the value is null and the option to return a reserved reference (like DBNull.Value) is enabled, return that reference. Otherwise, proceed as it currently does.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 16-Dec-2005 16:10:53   

AdamRobinson wrote:

I have to question that last portion of your post because of one issue: if that is true, then they could never bind to a DataView.

Sure it can, because it tests for a dataview.

Look, by the book every grid or other control in complex binding, has to use ITypedList, the property descriptors returned and what not. In practise, this isn't the case sadly enough. Just search on infragistics and 'hang' on this forum and you'll be presented by examples simple_smile .

They get better in respect to working with the standard way of doing things, but don't expect every grid does this. I had that hope as well, several years ago, but since then I learned the hard way that often reality is different: grids do all kinds of things to get binding to work, and often use special case code for datasets/views/tables and other code for collections, while it shouldn't make a difference.

The actual objects in a DataView are DataRowView objects, which do not have explicitly declared properties for each column, so reflection alone would fail to find a property for any column, and the grid wouldn't display any data. I fail to see a way that you could bind to something generic like the DataView without playing nice with the PropertyDescriptor paradigm.


if(_dataSource is DataView)
{
// use dataview code
}
else
{
// use other code
}

and this is real. Grids get the properties using ITypedList.GetItemProperties, and llblgen pro implements that, and you can alter the behavior as I described. However, just as an example, UltraGrid from infragistics doesn't use ITypedList to get the properties, it adds a new instance to the collection and reflects on it. This hangs up selfservicing apps for example because the reflection triggers lazy loading if they reflect multiple bands in one go.

We did everything we could to prevent this by implementing ITypedList, IBindingList and what not, but some simply won't use it. Their latest versions seem to be alittle friendlier, but earlier versions (2004.x for example) aren't. It was a surprise for me too, because why 2 codebases if you can do everything with ITypedList simple_smile

Oh, and the columns in a grid can also be retrieved from the related datatable's datacolumn collection, without property descriptors. Several report generators use that kind of technique to work with a bound datasource, instead of working with ITypedlist so everything would work out of the box.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 65
Joined: 07-Dec-2005
# Posted on: 16-Dec-2005 16:26:35   

Those are good points. I suppose I just hadn't put on the right hat to think about that stuff wink

Thanks, I'll look into doing my own propertydescriptor. I'm hoping the Janus GridEx uses them!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 16-Dec-2005 17:25:38   

AdamRobinson wrote:

Those are good points. I suppose I just hadn't put on the right hat to think about that stuff wink

Consider yourself lucky wink Databinding is very obscure stuff and I advice everyone to stay away from low-level databinding code as far as possible wink . It's not well documented (no tutorial using ITypedList for example), the design is obscure as well, as teh GetItemProperties' parameters are hardly used for the purpose they're designed for and a lot of the grids out there don't do what's expected although the .NET grid does (most of the time wink )

Thanks, I'll look into doing my own propertydescriptor. I'm hoping the Janus GridEx uses them!

Yes they do simple_smile . And if not, I'll tell Jose they should wink

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 03-Feb-2006 13:45:44   

I have just started playing with LLBLGenPro and found a possible solution for Null Value types (until Net 2.0 nullable types are supported).

1) Put the template code listed below into an lpt file called entityNullableFields.lpt 2) Add a line to CSharpTemplateSet.config to map the file to a templateID <templateBinding templateID="NullableFieldsTemplate" templateFilename="<your path here>\entityNullableFields.lpt" /> 3) Include the template in entityInclude.template by finding the bottom of the section that creates the properties, around line 1159, and change the last line to: }<# NullableFieldsTemplate #><[NextForeach]>

This will generate an addition property for any value type properties that are nullable called <NormalPropertyName>Nullable. Use this property for binding rather than the normal property name.

I've only given this a quick test but it worked fine in DevExpress Grid/TextEdit/SpinEdit/DateEdit controls. I can use Ctrl-Del or Ctrl-0 or just delete the text and the result is a Null in the Database.

More work (and testing) needs to be done on this of course but looks promising so far. Todo: Needs proper events to match normal properties for completeness (although I had a TextEdit and a SpinEdit bound to the same Nullable property and changing one changed the other anyway) I should maybe look at providing the new Nullable properties via ICustomTypeDescriptor only and specifying [Bindable(false)] on normal properties which have a corresponding NullableProperty - but this might introduce problems of its own.

Cheers Simon

<% Hashtable state = (Hashtable) _activeObject; EntityFieldDefinition field = state["CurrentEntityField"] as EntityFieldDefinition; if (field != null && field.MappedFieldIsNullable && field.DotNetType.IsValueType) { string entityName = field.ContainingEntityName; string propertyOverride = field.Overrides ? "override" : "virtual";%>

    public <%=propertyOverride%> object <%=field.FieldName%>Nullable {
        get {
            if (TestCurrentFieldValueForNull(<%=entityName%>FieldIndex.<%=field.FieldName%>))
                return null;
            else {
                return GetCurrentFieldValue((int)<%=entityName%>FieldIndex.<%=field.FieldName%>);
            }
        }
        set {
            if (value == null || value == DBNull.Value || value.ToString().Length == 0)
                SetNewFieldValue((int)<%=entityName%>FieldIndex.<%=field.FieldName%>, null);
            else {
                SetNewFieldValue((int)<%=entityName%>FieldIndex.<%=field.FieldName%>, Convert.ChangeType(value, typeof(<%=field.DotNetType%>)));
            }
        }
    }

<% } %>

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 05-Feb-2006 12:30:02   

Cool stuff and thanks for sharing! smile

Frans Bouma | Lead developer LLBLGen Pro
Angus
User
Posts: 44
Joined: 08-Jul-2005
# Posted on: 28-Mar-2006 17:13:40   

Simmon,

Great work around. This is what I also have been looking for. I just wanted to post an update for those of us who are using the Adapter pattern. You said....

simmotech wrote:

3) Include the template in entityInclude.template by finding the bottom of the section that creates the properties, around line 1159, and change the last line to: }<# NullableFieldsTemplate #><[NextForeach]>

but for the Adapter pattern the Included template is "entityAdapter.template" and the line of code is around 740. The change is the same on that line as well.

        }<# NullableFieldsTemplate #><[NextForeach]>

Cheers.