Databinding with Nested Objects

Posts   
 
    
jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 20-Mar-2007 17:18:40   

Hello.

I'm evaluating LLBLGen Pro for use at our company (seems like a great product so far). I read through the Concepts section of the documentation, and I understood most of the concepts.

My question is regarding databinding, specifically using Windows Forms, C#.NET 2.0, and the SelfServicing template group (also we're using SQL Server 2000/2005).

Here is my object graph -- it's pretty straight forward:

  • Order
    • [m:1] User
      • [m:1] State
    • [1:n] Detail
      • [m:1] Product

At this point, I just want to have a combobox list of all of the order IDs, and then a bunch of labels displaying the order information, user information for the user who placed the order, and the state name associated to the user. I will tackle the order details once I get past this point (and I may be able to figure that out, once I figure out the "basic" stuff).

In order to populate the order ID combobox, I did the following:

  • Set up a orderCollection control on my form
  • Set up a BindingSource control, and set its DataSource to orderCollection1
  • Set up a combobox control, and set its DataSource to bindingSource1, DisplayMember to "Id", and ValueMember also to "Id"
  • Added code to populate the combobox in the Form OnLoad event:
orderCollection1.GetMulti(null);

This works fine. First question, is this the "best practice" approach, or is there a better way to do this?

Next, I have my labels that I want to populate. For example, I have a "User Name" label that I want to populate with: orderCollection1[selectedIndex].User.Name. And then I have a "State" label that I want to populate with: orderCollection1[selectedIndex].User.State.Name.

Of course, I can achieve this in the backend, but I'm assuming there's a way to do the binding at design time. But when I pull up the DataBinding options, bindingSource1 is only populated with the root-level properties of orderCollection1, and doesn't allow me to traverse through the related objects.

So my second question is, am I doing something wrong that is preventing me from accessing the nested objects, or must I access them via another method? Or, am I left with having to manually bind these at run time?

I tried finding the answers to this both on this forum and in the documentation, but couldn't find them (may not be looking in the right places). If this is listed somewhere, please let me know, and I will look deeper rather than wasting your time on repeating what is already discussed somewhere else.

Thanks in advance for your suggestions.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 21-Mar-2007 10:34:24   

jader201 wrote:

Hello.

I'm evaluating LLBLGen Pro for use at our company (seems like a great product so far). I read through the Concepts section of the documentation, and I understood most of the concepts.

My question is regarding databinding, specifically using Windows Forms, C#.NET 2.0, and the SelfServicing template group (also we're using SQL Server 2000/2005).

Here is my object graph -- it's pretty straight forward:

  • Order
    • [m:1] User
      • [m:1] State
    • [1:n] Detail
      • [m:1] Product

At this point, I just want to have a combobox list of all of the order IDs, and then a bunch of labels displaying the order information, user information for the user who placed the order, and the state name associated to the user. I will tackle the order details once I get past this point (and I may be able to figure that out, once I figure out the "basic" stuff).

In order to populate the order ID combobox, I did the following:

  • Set up a orderCollection control on my form
  • Set up a BindingSource control, and set its DataSource to orderCollection1
  • Set up a combobox control, and set its DataSource to bindingSource1, DisplayMember to "Id", and ValueMember also to "Id"
  • Added code to populate the combobox in the Form OnLoad event:
orderCollection1.GetMulti(null);

This works fine. First question, is this the "best practice" approach, or is there a better way to do this?

If you're not going to re-use the order entity objects in the collection, you also could opt for a dynamic list with just the ID field, which saves you the retrieval of the other order fields.

Next, I have my labels that I want to populate. For example, I have a "User Name" label that I want to populate with: orderCollection1[selectedIndex].User.Name. And then I have a "State" label that I want to populate with: orderCollection1[selectedIndex].User.State.Name.

Of course, I can achieve this in the backend, but I'm assuming there's a way to do the binding at design time. But when I pull up the DataBinding options, bindingSource1 is only populated with the root-level properties of orderCollection1, and doesn't allow me to traverse through the related objects.

So my second question is, am I doing something wrong that is preventing me from accessing the nested objects, or must I access them via another method? Or, am I left with having to manually bind these at run time?

Great huh, .NET databinding wink The thing is that binding to a textbox is 'simple binding' which means you can bind a property from source (bindingsource) to a property from target (textbox).

You can't simply setup a binding from datasource.property.property.property to textbox.text, that's not going to work in .NET, so that's also why vs.net doesn't allow you to do that.

In short: - prefetch the data in the graph OR create a typedlist - bind manually if you fetch a graph of objects, or if you've made a typedlist (which is readonly) you can simply bind the fields to the field of the typedlist

Frans Bouma | Lead developer LLBLGen Pro
Posts: 254
Joined: 16-Nov-2006
# Posted on: 21-Mar-2007 11:59:35   

Has anyone reviewed this, I think this should work with nested properties although need to review further.

http://msdn.microsoft.com/msdnmag/issues/06/12/ExtendASPNET/default.aspx?loc=en

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 21-Mar-2007 12:01:21   

Otis wrote:

Great huh, .NET databinding wink The thing is that binding to a textbox is 'simple binding' which means you can bind a property from source (bindingsource) to a property from target (textbox).

You can't simply setup a binding from datasource.property.property.property to textbox.text, that's not going to work in .NET, so that's also why vs.net doesn't allow you to do that.

Hi Frans

Its perfectly possible to bind to x levels deep in .Net binding. I've attached a simple .NET 1.1 form class which shows binding TextBoxes to 3 levels and a grid and a combo box to allow selection of the current item.

I think the thing about 'simple' binding refers, as you say, to the control's ability to show a single element from the 'boundobject' whereas complex binding controls can show multiple elements from the 'boundobject'. However, the bound property does not _have _to be on the 'boundobject' itself - that is just a starting point and you can specify multiple levels (as long as the indirect objects are non-null!) to the target property. Also, the 'boundobject' is not necessarily static - if the dataSource is a list then the CurrencyManager will update the 'boundobject' according to the list position; simple binding controls still work and automatically show/update the 'boundobject'

Cheers Simon

Attachments
Filename File size Added on Approval
Form1.cs 10,163 21-Mar-2007 12:02.13 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 21-Mar-2007 12:34:01   

simmotech wrote:

Otis wrote:

Great huh, .NET databinding wink The thing is that binding to a textbox is 'simple binding' which means you can bind a property from source (bindingsource) to a property from target (textbox).

You can't simply setup a binding from datasource.property.property.property to textbox.text, that's not going to work in .NET, so that's also why vs.net doesn't allow you to do that.

Hi Frans

Its perfectly possible to bind to x levels deep in .Net binding. I've attached a simple .NET 1.1 form class which shows binding TextBoxes to 3 levels and a grid and a combo box to allow selection of the current item.

Hmm, you're right. I stand corrected! flushed

However, how to set it up at design time? (I think that's why I was under the impression it doesn't work) -> I hide the m:1/1:1 related entities from ITYpedList data, using Browsable(false), otherwise they'll end up in grids as columns (not fun), however even if I remove that attribute for testing, I only see the property in the list (I used 'order' and 'customer'): I only see 'Customer' as a property of the datasource, I can't navigate to the customer fields.

So setting this up can only be done manually IMHO, not via design time designers.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 21-Mar-2007 13:08:28   

Otis wrote:

simmotech wrote:

Otis wrote:

Great huh, .NET databinding wink The thing is that binding to a textbox is 'simple binding' which means you can bind a property from source (bindingsource) to a property from target (textbox).

You can't simply setup a binding from datasource.property.property.property to textbox.text, that's not going to work in .NET, so that's also why vs.net doesn't allow you to do that.

Hi Frans

Its perfectly possible to bind to x levels deep in .Net binding. I've attached a simple .NET 1.1 form class which shows binding TextBoxes to 3 levels and a grid and a combo box to allow selection of the current item.

Hmm, you're right. I stand corrected! flushed

However, how to set it up at design time? (I think that's why I was under the impression it doesn't work) -> I hide the m:1/1:1 related entities from ITYpedList data, using Browsable(false), otherwise they'll end up in grids as columns (not fun), however even if I remove that attribute for testing, I only see the property in the list (I used 'order' and 'customer'): I only see 'Customer' as a property of the datasource, I can't navigate to the customer fields.

So setting this up can only be done manually IMHO, not via design time designers.

Well, I'm not sure what should happen, but I've just had a quick look at the source code for 2.0 (.NET 1.1): You seem to be using the **last ** PropertyDescriptor in the listAccessors argument, and then returning a standard PropertyDescriptionCollection based on one of a number of criteria - presence of TypeContainedAttribute; IEntityCollection; IEntityViewSource; ITypedList. Also, the GetPropertyDescriptors on EntityViewBase seems to specifically ignore IEntityCollection and IEntityCollection2 properties when in DesignMode and the number of listAccessors >=2

The latter is probably the reason they don't show in the designer? But also, reflectoring DataView shows that it uses each and every listAccessor passed - this may or may not be relevant though.

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 21-Mar-2007 13:53:01   

simmotech wrote:

Otis wrote:

simmotech wrote:

Otis wrote:

Great huh, .NET databinding wink The thing is that binding to a textbox is 'simple binding' which means you can bind a property from source (bindingsource) to a property from target (textbox).

You can't simply setup a binding from datasource.property.property.property to textbox.text, that's not going to work in .NET, so that's also why vs.net doesn't allow you to do that.

Hi Frans

Its perfectly possible to bind to x levels deep in .Net binding. I've attached a simple .NET 1.1 form class which shows binding TextBoxes to 3 levels and a grid and a combo box to allow selection of the current item.

Hmm, you're right. I stand corrected! flushed

However, how to set it up at design time? (I think that's why I was under the impression it doesn't work) -> I hide the m:1/1:1 related entities from ITYpedList data, using Browsable(false), otherwise they'll end up in grids as columns (not fun), however even if I remove that attribute for testing, I only see the property in the list (I used 'order' and 'customer'): I only see 'Customer' as a property of the datasource, I can't navigate to the customer fields.

So setting this up can only be done manually IMHO, not via design time designers.

Well, I'm not sure what should happen, but I've just had a quick look at the source code for 2.0 (.NET 1.1): You seem to be using the **last ** PropertyDescriptor in the listAccessors argument, and then returning a standard PropertyDescriptionCollection based on one of a number of criteria - presence of TypeContainedAttribute; IEntityCollection; IEntityViewSource; ITypedList.

Yes, as that's the only one to use. The rest is the path towards that node in the tree. But ITypedList.GetItemProperties is only called on IList implementing constructs, not normal objects if I'm not mistaken.

So if you have a customer collection and in there in each customer an order collection, and you browse to order, the listaccessors is length 2, and has as start customer and as last node order.

Also, the GetPropertyDescriptors on EntityViewBase seems to specifically ignore IEntityCollection and IEntityCollection2 properties when in DesignMode and the number of listAccessors >=2

The latter is probably the reason they don't show in the designer?

No, it's to work around a stupid bug in vs.net where it dives into an infinite loop. Take for example Customer - Employee which is an m:n relation. If I didn't have this limitation, vs.net will dive into an infinite loop because customer references employee collection, which references customer which refe...

The stupid thing is that even if I return the same property descriptors, it doesn't matter. NOTHING stops it, so I had to implement this.

But this isn't the reason. It simply doesn't reflect in the property grid over the members of a related object of an entity apparently. 'Data sources' does, though that will not help either because it works on types, not a hierarchy, so you have to bind the data yourself then anyway.

But also, reflectoring DataView shows that it uses each and every listAccessor passed - this may or may not be relevant though. Cheers Simon

It has to because it can't find a related table otherwise. I don't have to, as the type to investigate is available to me in the listaccessors element at the end.

ITypedList is a weird interface, no documentation and odd behavior at runtime or worse: at design time. MS also changed behavior of the designers completely in VS.NET 2005, and it's basicly a matter of trial/error/decompile.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 21-Mar-2007 14:25:38   

I feel you pain re. Binding. confused

I think the infinite loop you encountered is avoided by Microsoft's DataTable/DataViews because they use custom PropertyDescriptors - DataTablePropertyDescriptor and DataRelationPropertyDescriptor. The DataSet.FindTable internal method only uses the latter's Relation.ChildTable property and never ParentTable so maybe thats why there is no loop.

'Data sources' does, though that will not help either because it works on types, not a hierarchy, so you have to bind the data yourself then anyway.

Not sure exactly what you mean by this. I've only played with it a couple of times but ISTR it did have a hierachy. So if I had a Customer which had an Address property of type Address then I could select the fields from Address. Maybe it doesn't work for List properties or list properties where the type is not known??

Cheers Simon

jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 21-Mar-2007 21:35:42   

So, is there a way to get this to work with my example above?

For example, I tried the following:

userNameLabel.DataBindings.Add("Text", bindingSource1, "User.Name");

But it gave me this error:

Child list for field User cannot be created.

I thought that this would work using design-mode binding, because this same hierarchical/nested binding works in another O/RM product fairly seamlessly. I'm not sure if it's using the .NET binding controls, or if it's using its own proprietary controls for achieving this, but it can be done without the need of wiring the binding through code.

But even if I could accomplish the binding through code, I would be satisfied (as in, setting the binding and forgetting about it on control or object updates). Otherwise, I will be left with manually managing the concurrency between the controls and the objects, which puts me back in the same situation pre-O/RM tools.

If you guys have any example projects that show how this is accomplished (I didn't see any with the LLBLGen Pro Demo installation or documentation), that would be most helpful.

Thanks again for your input. I'd really like to figure out how to accomplish this, as it would save quite a bit of tedious development/maintenance time for our company.

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 22-Mar-2007 07:17:49   

jader201 wrote:

So, is there a way to get this to work with my example above?

For example, I tried the following:

userNameLabel.DataBindings.Add("Text", bindingSource1, "User.Name");

But it gave me this error:

Child list for field User cannot be created.

I'm interested in this too.

How is bindingSource1 configured? What is the exact type of orderCollection? Have you tried using the orderCollection as the datasource rather than bindingSource1 just to see if you get the same error message?

Cheers Simon

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 22-Mar-2007 08:48:58   

Started a little research on this (but will have to get back to the day job now).....

I am testing a 1:1 relationship for now. VoyageEntity has a StartPort property (of type RdPortEntity) I have fetched 10 VoyageEntity records from the database and a combobox is used to let me choose the current VoyageEntity. I am binding a TextBox to the PortNm string property on RdPortEntity: txtStartPortName.DataBindings.Add("Text", dataSource, "StartPort.PortNm");

Fours things found so far: 1) The "Child list for field XXX cannot be created." error is definitely down to the Browseable[false] attribute.

2) Once that attribute is removed then I get this error when the DataSource is the EntityCollection<VoyageEntity>:

"Cannot bind to the property or column PortNm on the DataSource. Parameter name: dataMember"

3) If I set the DataSource to be an array of the 10 VoyageEntitys then binding works fine.

4) There is definitely a problem within the EntityViewBase<TEntity> implementation of ITypedList (at least for 1:1 objects). If I just comment out the ITypedList declaration, then nested binding works.

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 22-Mar-2007 09:39:04   

simmotech wrote:

Started a little research on this (but will have to get back to the day job now).....

I am testing a 1:1 relationship for now. VoyageEntity has a StartPort property (of type RdPortEntity) I have fetched 10 VoyageEntity records from the database and a combobox is used to let me choose the current VoyageEntity. I am binding a TextBox to the PortNm string property on RdPortEntity: txtStartPortName.DataBindings.Add("Text", dataSource, "StartPort.PortNm");

Fours things found so far: 1) The "Child list for field XXX cannot be created." error is definitely down to the Browseable[false] attribute.

I can't remove this. It will be a true pain in other areas for people using design time databinding, as these columns then show up in grids.

2) Once that attribute is removed then I get this error when the DataSource is the EntityCollection<VoyageEntity>:

"Cannot bind to the property or column PortNm on the DataSource. Parameter name: dataMember"

I have no idea why this is.

3) If I set the DataSource to be an array of the 10 VoyageEntitys then binding works fine.

4) There is definitely a problem within the EntityViewBase<TEntity> implementation of ITypedList (at least for 1:1 objects). If I just comment out the ITypedList declaration, then nested binding works.

Couldn't this be a problem with the currencymanager instead? I mean: all GetItemProperties does is provide property descriptors of all the properties in the node. For example, it will return a property descriptor for the StartPort property. The currencymanager (or whatever object this does) then has to see if it can navigate further.

So it has to reflect (IMHO) over StartPort and show these properties. However it doesn't do that. Perhaps because ITypedList is implemented and the currency manager leaves everything to that, but I'm not sure. Anyway, I would be very surprised if the GetItemProperties gets a call for StartPort's properties, as the ITypedList interface is used for multi-band set traversal.

The reason I think that is because if you remove Browsable(false), and you go to the DataSources window in vs.net (via the Data menu) and add a datasource of 'object' and select VoyageEntity, you WILL see the properties of StartPort.

(edit). The main thing why things go wrong is IMHO these two things: - With a dataset and datarelations, you never have 1:1 objects, there's always a set on the other end (datatable) - with a dataset, you never have loops as you don't have m:n relations - without ITypedList, the currencymanager HAS TO reflect over the object to get the properties, so it will do so for the complete graph. this is why it does work with plain vanilla objects. The downside is that this will trigger lazy loading and probably other stuff, so you want type traversal instead. However, that IMHO blocks 1:1 object property discovery.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 22-Mar-2007 11:11:12   

Otis wrote:

simmotech wrote:

1) The "Child list for field XXX cannot be created." error is definitely down to the Browseable[false] attribute.

I can't remove this. It will be a true pain in other areas for people using design time databinding, as these columns then show up in grids.

Well I understand that is the case when you bind a grid without specifying which items you require but from my own point of view: 1) I know from my own experience that I would never bind a grid to an object without specifying exactly what columns I want and how they should be configured. 2) It scuppers (nested) DataSource binding which is a big thing in .NET 2.0 3) It also hides properties like IsNew or IsDirty which are sometimes useful 4) Sometimes, it is useful to bind to an object-returning property in a grid - it will display the ToString() string as a readonly value - useful to display summary information about a related entity directly in the grid without having to add properties to the main object 5) Further to (4), I have some code I will be adding via the WithCommonBase preset so that all entities will be IFormattable and will support returning any string built-up from any property /properties on the object - just changing the formatstring will return a different string.

The point of design-time databinding, to me, is that all available properties should be displayed and then you trim them down to what you actually need - hiding some of them defeats that point.

I just tried using an EntityCollection with a BindingSource, bound to a DevExpress grid and a DataGridView - was easy to just remove the now visible StartPort column from each view.

Also, I tried overriding ToString() on RdPortEntity to return the PortNm property - worked on both grids and both with and, interestingly, without ITypedList support. Now since the DevExpress grid also picks up and displays a child table (PortCalls) both with and without ITypedList support (not sure how DataGridView works with child object) - what is the purpose of ITypedLists again???

Cheers Simon

PS This was written before I saw your (edit) so the final question is probably answered - to prevent lazy-loading in Self-Servicing.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 22-Mar-2007 12:19:12   

(edited)

simmotech wrote:

Otis wrote:

simmotech wrote:

1) The "Child list for field XXX cannot be created." error is definitely down to the Browseable[false] attribute.

I can't remove this. It will be a true pain in other areas for people using design time databinding, as these columns then show up in grids.

Well I understand that is the case when you bind a grid without specifying which items you require but from my own point of view: 1) I know from my own experience that I would never bind a grid to an object without specifying exactly what columns I want and how they should be configured.

Me neither, but a lot of people do. They want to setup the columns at design time, and there these columns have to be ignored as well, also at runtime, if you simply auto-discover the types, it should work, you don't want odd columns to show up. (we did this in early versions, people didn't like that wink )

2) It scuppers (nested) DataSource binding which is a big thing in .NET 2.0

Trust me, if I could solve ALL databinding problems for once, I would be a happy man. However there's always something else thats apparently not working, and if you fix one thing, it breaks another. disappointed

3) It also hides properties like IsNew or IsDirty which are sometimes useful

But not always. The sad thing is: I can't make a right decision here: if I drop [browsable(false)] not only will a lot of applications suddenly misbehave which rely on this attribute, but also some people will be very unhappy as designing the forms will be more cumbersome. I find the whole design time experience cumbersome no matter what so I don't mind removing 1 or 2 columns, but others do.

So rock, hard place... simple_smile

4) Sometimes, it is useful to bind to an object-returning property in a grid - it will display the ToString() string as a readonly value - useful to display summary information about a related entity directly in the grid without having to add properties to the main object

True, but as I said, I don't see a proper solution to solve everyone's problems: if I drop the browsable(false), there will be other people very unhappy, if I keep it, it probably will ruin someone elses design time experience.

5) Further to (4), I have some code I will be adding via the WithCommonBase preset so that all entities will be IFormattable and will support returning any string built-up from any property /properties on the object - just changing the formatstring will return a different string.

The point of design-time databinding, to me, is that all available properties should be displayed and then you trim them down to what you actually need - hiding some of them defeats that point.

Not true IMHO (well your opinion is of course, but that hiding is actually not standard). If you look at what properties a DataTable/View expose, and which you'll see in a grid, you'll notice that the columns you see are the columns in the datatable. There are no other columns. No state, nothing. If you want these, you're out of luck. Though, people seem to like this approach as (I guess) in most cases it is the situation they want to work with.

If I only could mark them as 'potential' or something, so people wouldn't see them at first but COULD add them at design time, it would all be solved. However I can't, it's either it shows up or it is not there at all.

I just tried using an EntityCollection with a BindingSource, bound to a DevExpress grid and a DataGridView - was easy to just remove the now visible StartPort column from each view.

Also, I tried overriding ToString() on RdPortEntity to return the PortNm property - worked on both grids and both with and, interestingly, without ITypedList support. Now since the DevExpress grid also picks up and displays a child table (PortCalls) both with and without ITypedList support (not sure how DataGridView works with child object) - what is the purpose of ITypedLists again???

PS This was written before I saw your (edit) so the final question is probably answered - to prevent lazy-loading in Self-Servicing.

That's correct, plus filtering out Browsable(false) properties plus providing our own property descriptors at runtime, so columns with readonly fields are also readonly.

This last point is however a bit moot in adapter, as the readonly fields have to be writable if the entity is new. The thing is though that this discovery is done without data, and at runtime only the FIRST row decides what happens for the whole grid, so it's not really a key issue.

I can't switch off the ITypedList implementation, that's necessary. The main reason an entityview implements ITypedList is that it by itself doesn't have any properties which are interesting, you want the properties of the ENTITY inside the view. This requires ITypedList, you can't tell a bound control otherwise which properties there are in the object inside the view, not without data at runtime and also not without data at design time.

The entitycollection and entityview are necessarily implementing ITypedList to be able to tell which properties to bind to when there's no data, as they're generic containers. This means that a Customer.Order collection bound to a grid and there are no rows (e.g. at design time or at runtime) the columns are still available. Some grids (not all) try to grab the type of the object inside the collection. That's not always possible (e.g. the collection is an ArrayList) though when it does succeed, they create an instance of that and reflect over that.

ITypedList helps in this, as it tells the bound grid which columns to display, when there's no data in the collection, and when the grid doesnt' support instantiation of the type. (which sometimes isn't possible as they require an empty constructor).

With textbox binding, it of course has no meaning. Thats also why I don't understand why it doesn't work, even without [browsable(false)]. At least at design time.

Edit One BIG thing to understand is that design time databinding in vs.net doesn't support generics. This means that an entity collection dragged onto the form is the nongeneric one, which returns EntityBase2 not the entity of the factory of the collection. This thus means that the view is nongeneric as well (it uses a base type, not the entity type). So type discovery isn't possible there based on the entity type of the collection, as that's not there.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 22-Mar-2007 14:41:02   

I think backwards compatibility plus your point about how DataView only return columns not infrastructure (I only half agree with this reasoning though simple_smile ) stops any changes that would help solve jader201's problems. However....

This thread has just reminded me of a problem I had which was the opposite of what we discussed re. show all columns/remove unwanted: we needed to configure grids for entities in an in-house ORM system (Igloo) but the collection class was very simple and did not support ITypedList and we were(are!) stuck with .Net 1.1 and so no BindingSource. Our problem was that we had no design-time columns auto-populated - we had to hand code them all! So I wrote a class which derived from Component so it could be dropped onto a form. You then select an entity type from a drop-down list and the class 'pretends' to be a collection of those entity types. The grid's (Janus cry ) datasource could then be bound to this component and all of the columns were populated. The component is then deleted but all the column definitions remain. wink Other advantages were that I added some boolean properties so the developer could decide in the PropertyGrid whether or not to include certain infrastructure items such as Modified and PrimaryKey etc. All PropertyDescriptors created are then 'filtered' based on these options before a final list is returned. I also added an option to auto-generate a DisplayName for the property descriptors which some grids will use as the caption when present. So a property called "PortName" got a DisplayName of "Port Name" on its returned PropertyDescriptor and so 95% of the Grid columns' captions were correctly set rather than having to manually change them all.

Maybe the above ideas could be incorporated into new BindingCollection/BindingView classes (or maybe just deriving from the existing would do - I see that GetPropertyDescriptors on EntityViewBase is virtual) so that the developer can choose at design time (or runtime) whether to include infrastructure items or not, whether to auto-gen DisplayNames, provide a list of strings of property names to include/exclude or whatever.

Basically, it puts the Browsable(false) back in the hands of the developer (and adds more flexibility for good measure). Nested binding could be made to work and grids would have more options as to what to include/not include by default. And, maybe more importantly, DataSource would be useable.

What do you think, v2.2 maybe.smile

Cheers Simon

PS In fact this virtual mechanism would be also be good for other Binding issues since virtual properties could be 'injected' via a custom PropertyDescriptor or existing ones 'customized'. For example, off the top of my head, properties could become readonly if the user does not have permissions to modify it; DateTime properties could be magically converted to local time for display/editing purposes and then put back to UTC time within the entity. Since the "x.y" nested binding path doesn't work for grids only simple-bound items, there is an opportunity to create virtual properties called "x_y" instead for 1:1 bound objects that would be treated, from the grid's point of view, as part of the root object. None of this has to be used but it would at least give the option.

jader201
User
Posts: 33
Joined: 20-Mar-2007
# Posted on: 22-Mar-2007 19:32:21   

simmotech wrote:

I'm interested in this too.

How is bindingSource1 configured?

It's pretty straight forward, I listed above how the controls were set up:

  • Set up a orderCollection control on my form (orderCollection1)
  • Set up a BindingSource control (bindingSource1), and set its DataSource to orderCollection1
  • Set up a combobox control (ordersCombo), and set its DataSource to bindingSource1, DisplayMember to "Id", and ValueMember also to "Id"
  • Added code to populate the combobox in the Form OnLoad event:
orderCollection1.GetMulti(null);

simmotech wrote:

What is the exact type of orderCollection?

orderCollection is of type DataAccess.CollectionClasses.OrderHeaderCollection, which inherits EntityCollectionBase<OrderHeaderEntity>. OrderHeaderEntity inherits OrderHeaderEntityBase, which looks something like this (irrelevant members removed):

int Id;
DateTime OrderDate;
UserEntity User;
MyDataAccess.CollectionClasses.OrderDetailCollection Details;

simmotech wrote:

Have you tried using the orderCollection as the datasource rather than bindingSource1 just to see if you get the same error message?

Yeah, I actually tried a few different scenarios.

bindingSource, no prefetch:

orderCollection1.GetMulti(null);
userNameLabel.DataBindings.Add("Text", bindingSource1, "User.Name");

ERROR: DataMember property 'User' cannot be found on the DataSource.

orderCollection, no prefetch:

orderCollection1.GetMulti(null);
userNameLabel.DataBindings.Add("Text", orderCollection1, "User.Name");

ERROR: Child list for field User cannot be created.

bindingSource, w/ prefetch:

PrefetchPath prefetchPath = new PrefetchPath((int)EntityType.OrderEntity);
prefetchPath.Add(OrderEntity.PrefetchPathRegistration);
orderCollection1.GetMulti(null, prefetchPath);

userNameLabel.DataBindings.Add("Text", bindingSource1, "User.Name");

ERROR: DataMember property 'User' cannot be found on the DataSource.

orderCollection, w/ prefetch:

PrefetchPath prefetchPath = new PrefetchPath((int)EntityType.OrderEntity);
prefetchPath.Add(OrderEntity.PrefetchPathRegistration);
orderCollection1.GetMulti(null, prefetchPath);

userNameLabel.DataBindings.Add("Text", orderCollection1, "User.Name");

ERROR: Child list for field User cannot be created.

The data is there, as I can access it via code. I.e., this works just fine:

userNameLabel.Text = orderCollection1[0].Registration.Name;

So I'm still pretty lost as to how to get this to work, design-time or run-time.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 23-Mar-2007 11:15:04   

simmotech wrote:

I think backwards compatibility plus your point about how DataView only return columns not infrastructure (I only half agree with this reasoning though simple_smile ) stops any changes that would help solve jader201's problems. However....

I'm interested in the parts you don't agree with simple_smile Could you elaborate on that abit?

This thread has just reminded me of a problem I had which was the opposite of what we discussed re. show all columns/remove unwanted: we needed to configure grids for entities in an in-house ORM system (Igloo) but the collection class was very simple and did not support ITypedList and we were(are!) stuck with .Net 1.1 and so no BindingSource. Our problem was that we had no design-time columns auto-populated - we had to hand code them all! So I wrote a class which derived from Component so it could be dropped onto a form. You then select an entity type from a drop-down list and the class 'pretends' to be a collection of those entity types. The grid's (Janus cry ) datasource could then be bound to this component and all of the columns were populated. The component is then deleted but all the column definitions remain. wink Other advantages were that I added some boolean properties so the developer could decide in the PropertyGrid whether or not to include certain infrastructure items such as Modified and PrimaryKey etc. All PropertyDescriptors created are then 'filtered' based on these options before a final list is returned. I also added an option to auto-generate a DisplayName for the property descriptors which some grids will use as the caption when present. So a property called "PortName" got a DisplayName of "Port Name" on its returned PropertyDescriptor and so 95% of the Grid columns' captions were correctly set rather than having to manually change them all.

Maybe the above ideas could be incorporated into new BindingCollection/BindingView classes (or maybe just deriving from the existing would do - I see that GetPropertyDescriptors on EntityViewBase is virtual) so that the developer can choose at design time (or runtime) whether to include infrastructure items or not, whether to auto-gen DisplayNames, provide a list of strings of property names to include/exclude or whatever.

Binding with another class isn't doable as it would give problems with hierarchies at runtime I think (you fetch a customer and its orders, bind the customer's orders to a grid: that's an EntityView).

What's actually needed is an MVC pattern implementation, so a controller and a separate viewer, though winforms doesn't help in that, although you might see the bindingsource as the controller.

If I'm not mistaken, WPF native databinding also uses a datasource control similar to web binding, which means a controller is introduced so it might be solvable there, though WPF isn't a thing a lot of people will use today as there's no design support in vs.net

It might be a BindingSource like class (derived from it for example) is the proper solution, so it can set a flag inside the bound EntityView to also report the Browsable(false) properties, controlled via a property on the bindingsource.

Basically, it puts the Browsable(false) back in the hands of the developer (and adds more flexibility for good measure). Nested binding could be made to work and grids would have more options as to what to include/not include by default. And, maybe more importantly, DataSource would be useable. What do you think, v2.2 maybe.smile

It won't be in v2.1 indeed, I'll add it to the list of things to do post 2.1. simple_smile

PS In fact this virtual mechanism would be also be good for other Binding issues since virtual properties could be 'injected' via a custom PropertyDescriptor or existing ones 'customized'. For example, off the top of my head, properties could become readonly if the user does not have permissions to modify it; DateTime properties could be magically converted to local time for display/editing purposes and then put back to UTC time within the entity. Since the "x.y" nested binding path doesn't work for grids only simple-bound items, there is an opportunity to create virtual properties called "x_y" instead for 1:1 bound objects that would be treated, from the grid's point of view, as part of the root object. None of this has to be used but it would at least give the option.

We had that mechanism in selfservicing in 1.0, where you could inject your own propertyfactory, though no-one used it. simple_smile .

Property voodoo would indeed be possible then, though I doubt a lot of people would go that far. You can do so today as well though: derive your class from BindingSource, override GetItemProperties, and examine what you get back and inject/replace the properties you want to modify with your own descriptors. The thing is though: these descriptors are reported without knowing the data, so that's often a difference which won't give the results you might hope for.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 23-Mar-2007 11:16:12   

Jader: the problem isn't solvable at the moment, you have to navigate to the object manually and bind that object to controls.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 23-Mar-2007 13:04:30   

Otis wrote:

simmotech wrote:

I think backwards compatibility plus your point about how DataView only return columns not infrastructure (I only half agree with this reasoning though simple_smile ) stops any changes that would help solve jader201's problems. However....

I'm interested in the parts you don't agree with simple_smile Could you elaborate on that abit?

Certainly simple_smile . It was these quotes....

Otis wrote:

Not true IMHO (well your opinion is of course, but that hiding is actually not standard). If you look at what properties a DataTable/View expose, and which you'll see in a grid, you'll notice that the columns you see are the columns in the datatable. There are no other columns. No state, nothing. If you want these, you're out of luck. Though, people seem to like this approach as (I guess) in most cases it is the situation they want to work with.

If I only could mark them as 'potential' or something, so people wouldn't see them at first but COULD add them at design time, it would all be solved. However I can't, it's either it shows up or it is not there at all.

As I understand it (and I alternate between being sure and not so sure regularly!), DataTable/DataView don't have a real concept of 1:1 relationships - just parent/child relationships. So a 1:1 relationship is represented as a parent row having a single child row but there is no way for .NET/binding to 'know' that as it could be a 1:m relationship but there just happens to be one row for now. (Maybe there is some metadata to indicate one or the other but the result is the same - they will be linked via a Relationship and that relationship would likely shown in a grid as a child table but wouldn't an EntityView not even give this since it is filtered out? Hmmm, not sure)

My own belief is that EntityViews/EntityCollections/Entities are not DataViews/DataTables/DataRows - they are hierachies in their own right and need not be reduced to the lowest common denominator. Also their use is not necessarily for grid use only - nested binding is one example of this, DataSources another. But the current implementation of EntityCollection/EntityView precludes this by hiding properties (both by the Browseable attribute and by filtering) to emulate the .NET DataView-in-a-Grid way of looking at things which, as you say, is common.

You hinted that a solution that allowed the best of both worlds would be nice but not possible given the absolute nature of Browsable - hence my suggestion to possibly make the hiding optional.

Since 2.0 is already out there and since people seem to like this approach, it would probably be unwise to change EntityView. What I was suggesting was 1) Create a new class that is an exact copy of EntityView, say BindingEntityView 2) Add some properties that will control which properties are filtered out 3) Make certain methods protected/virtual so that developers can do their own filtering/injecting/tweaking where required 4) If testing showed that it produces exactly the same results as the original EntityView when configured just so (default settings), then consider letting it replace EntityView; if there are any differences in behaviour, keep it as a separate class available as an option to EntityView.

This is all talk on my part of course and there may be other reasons why this wouldn't work - uncertainty about how 3rd party-components make assumptions due to lack of MS documentation being just one - but the code I mentioned proved it is possible to adjust PropertyInfos on the fly.

I sort of agree about your last comment about overriding BindingSource.GetItemProperties and 'fiddling' there but thats the problem, what comes back from EntityView has already filtered and you can't get those PropertyInfos back. Better to do the 'fiddling' in (Binding)EntityView and then BindingSource can be used untouched. Same idea - different place.

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 27-Mar-2007 10:31:34   

simmotech wrote:

Otis wrote:

simmotech wrote:

I think backwards compatibility plus your point about how DataView only return columns not infrastructure (I only half agree with this reasoning though simple_smile ) stops any changes that would help solve jader201's problems. However....

I'm interested in the parts you don't agree with simple_smile Could you elaborate on that abit?

Certainly simple_smile . It was these quotes....

Otis wrote:

Not true IMHO (well your opinion is of course, but that hiding is actually not standard). If you look at what properties a DataTable/View expose, and which you'll see in a grid, you'll notice that the columns you see are the columns in the datatable. There are no other columns. No state, nothing. If you want these, you're out of luck. Though, people seem to like this approach as (I guess) in most cases it is the situation they want to work with.

If I only could mark them as 'potential' or something, so people wouldn't see them at first but COULD add them at design time, it would all be solved. However I can't, it's either it shows up or it is not there at all.

As I understand it (and I alternate between being sure and not so sure regularly!), DataTable/DataView don't have a real concept of 1:1 relationships - just parent/child relationships. So a 1:1 relationship is represented as a parent row having a single child row but there is no way for .NET/binding to 'know' that as it could be a 1:m relationship but there just happens to be one row for now. (Maybe there is some metadata to indicate one or the other but the result is the same - they will be linked via a Relationship and that relationship would likely shown in a grid as a child table but wouldn't an EntityView not even give this since it is filtered out? Hmmm, not sure)

I think you're right. Although the datatable has the concept of a unique constraint, I don't think their code is clever enough to check whether the FK fields are also forming a UC, which would imply a 1:1 relation (I haven't tested it though, but I would think they would be clever enough to detect that pk - pk relations would result in 1:1 relations, but then again, it's still between sets of data, so they might just choose the easy way out)

My own belief is that EntityViews/EntityCollections/Entities are not DataViews/DataTables/DataRows - they are hierachies in their own right and need not be reduced to the lowest common denominator. Also their use is not necessarily for grid use only - nested binding is one example of this, DataSources another. But the current implementation of EntityCollection/EntityView precludes this by hiding properties (both by the Browseable attribute and by filtering) to emulate the .NET DataView-in-a-Grid way of looking at things which, as you say, is common.

They're their own hierarchy, but they don't filter out anything, only ITypedList.GetItemProperties does. The interface ITypedList, together with IBindingList (and some other nasties) are solely meant for databinding scenario's or better: where an external object has to discover what it's dealing with.

I do agree with the fact that by filtering some things out in ITypedList.GetItemProperties, it's not that flexible anymore, but then again, what's the alternative? In .NET a MVC setup isn't really common, so leaving it to the controller isn't really helpful as most people simply want to do things in vs.net's designers, set things up there, even if it takes them more time than you'd think.

There are a lot of properties filtered out by Browsable(false). For example, the validator, the concurrencypredicatefactory and more, which will show up when they're not filtered out. If I control the controller as well, I could mark these with another attribute, filter them out and leave the other objects alone, but that's currently not that handy, as .NET databinding code (where ITypedList is added for, if databinding wasn't a necessity, I would have left out ITypedList completely!) expects the support code on the object bound, not in some controller.

You hinted that a solution that allowed the best of both worlds would be nice but not possible given the absolute nature of Browsable - hence my suggestion to possibly make the hiding optional.

If the binding is done via a controller which sets a flag inside the entity collection to not hide Browsable(false) marked properties, it could be solved.

Such a controller would be a very tiny subclass of BindingSource for example, as I won't add more code to such a controller than absolutely necessary. It's already a nightmare to have everything properly added to the toolbox, so if more objects have to be added to that toolbox, it will be more confusing for some users.

Since 2.0 is already out there and since people seem to like this approach, it would probably be unwise to change EntityView. What I was suggesting was 1) Create a new class that is an exact copy of EntityView, say BindingEntityView 2) Add some properties that will control which properties are filtered out 3) Make certain methods protected/virtual so that developers can do their own filtering/injecting/tweaking where required 4) If testing showed that it produces exactly the same results as the original EntityView when configured just so (default settings), then consider letting it replace EntityView; if there are any differences in behaviour, keep it as a separate class available as an option to EntityView.

That will give a problem when you want to set it up in code and all you have is customer.Orders. IMHO it's better to have a bindingsource derived class which simply sets a property inside the bound entityView and from then on entityview.GetItemProperties doesn't filter out browsable(false), or whatever that has to be filtered.

This is all talk on my part of course and there may be other reasons why this wouldn't work - uncertainty about how 3rd party-components make assumptions due to lack of MS documentation being just one - but the code I mentioned proved it is possible to adjust PropertyInfos on the fly.

add to that that WPF introduces a declarative way to define a datasource, similar to the ASP.NET datasource controls and the problem will solve itself over time wink

I sort of agree about your last comment about overriding BindingSource.GetItemProperties and 'fiddling' there but thats the problem, what comes back from EntityView has already filtered and you can't get those PropertyInfos back. Better to do the 'fiddling' in (Binding)EntityView and then BindingSource can be used untouched. Same idea - different place.

Though the GetItemProperties override of BindingSource could simply check if the bound datasource is an entityview, if so, set its filter flag and then simply call the datasource' GetItemProperties. With ITypedList, the direct bound object will perform the discovery of the complete path anyway, so you only have to set that object's filter flag. simple_smile

Frans Bouma | Lead developer LLBLGen Pro