EntityCollection<T>.ListChanged

Posts   
 
    
Nill
User
Posts: 7
Joined: 11-Oct-2006
# Posted on: 13-Dec-2006 15:02:40   

I've got a problem with databinding and EntityCollection<T>.

If I bind a ListBox to an EntityCollection<T>, the ListBox will correctly show all the elements in the collection.

If I programatically change one of the elements in my collection, I'd like the change to be reflected in the listbox (and in other bound controls).

However, the changes aren't reflected until I select another element in the listbox.

As far as I can see, the EntityCollection<T>.ListChanged event isn't called until I select another element in the listbox. The EntityBase2.NotifyPropertyChanged event is called immediately when I change the bound property.

Note: When binding to a DataTable, the changes are reflected immediately.

I've attached a demo application (MSSQL, AdventureWorks) showing the problem.

Regards, Nill

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 13-Dec-2006 17:58:45   

Hi Nill,

Please use a BindingSource with a non-Generic Collection. Then on the button_click, after you modify the value, use bindingSource.EndEdit();


((CustomersEntity)entityCollectionNonGeneric1[listBox1.SelectedIndex]).CompanyName = "Test";
bindingSource1.EndEdit();

Please refere to the following long thread about the causes of this: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7526

(edit) Also a generic collection will do, and the corresponding button event should look like the following:

            
EntityCollection<CustomersEntity> collection = (EntityCollection<CustomersEntity>)bindingSource1.DataSource;
((CustomersEntity)collection[listBox1.SelectedIndex]).CompanyName = "Test";
bindingSource1.EndEdit();           

Nill
User
Posts: 7
Joined: 11-Oct-2006
# Posted on: 14-Dec-2006 09:06:28   

Walaa wrote:

Please refere to the following long thread about the causes of this: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7526

I'm not sure the link refers to the same problem.

In the link, they discuss DataGridView and IEditableObject. I'm aware that editing a row in DataGridView is 'buffered' and won't be reflected to other controls before changing current row or calling EndEdit().

However, I'm not using DataGridView or other controls that implement IEditableObject (as far as I'm aware of).

If I use a DataTable and programatically modify the data in the rows, the changes are immediately reflected in ListViewControl and TextBox instances. I've just tried to add a DataGridView to my form and can see that this control also reflects programtic entity-modifications immediately when binding to a DataTable.

If I use EntityCollection<ContactEntity> as my datasource, modifications aren't reflected until I change row in either the ListViewControl or the newly added DataGridView. When I change row, the changes are reflected in all my controls.

It does work usning a BindingSource and calling BindingSource.EndEdit(). However, I have trouble understanding, why this extra code should be necessary when it works without BindingSource and EndEdit() when I bind to a DataTable.

This means that I have to write callback-code in my business-layer in order to tell my GUI-layer that some entities have changed and that it should refresh bindingsources by calling EndEdit().

In my oppinion this is an error in LLBLGen Pro.

Regards, Nill

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 14-Dec-2006 11:43:42   

It's not, here's the dataview approach:


public Form1()
{
    InitializeComponent();

    DataTable table = new DataTable();
    ResultsetFields fields = new ResultsetFields(2);
    fields.DefineField(ContactFields.ContactId, 0);
    fields.DefineField(ContactFields.FirstName, 1);
    new DataAccessAdapter().FetchTypedList(fields, table, null);
    DataView toBind = table.DefaultView; 
    listBox1.DataSource = toBind;
    listBox1.DisplayMember = ContactFields.FirstName.Name;
    textBox2.DataBindings.Add("Text", table, ContactFields.FirstName.Name);

    toBind.ListChanged += collection_ListChanged;
    foreach(DataRowView row in toBind )
      row.PropertyChanged += contact_PropertyChanged;
}

void contact_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
  listBox2.Items.Add("DataRowView.PropertyChanged");
}

void collection_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
{
  listBox2.Items.Add("DataView.ListChanged");
}

private void button1_Click(object sender, System.EventArgs e)
{
  DataView view = (DataView)listBox1.DataSource;
  view[listBox1.SelectedIndex]["FirstName"] = "Test";
}

exactly the same behavior.

Though indeed, when a datatable is bound, it's not showing this behavior, then it indeed shows the change immediately:


public Form1()
{
    InitializeComponent();

    DataTable table = new DataTable();
    ResultsetFields fields = new ResultsetFields(2);
    fields.DefineField(ContactFields.ContactId, 0);
    fields.DefineField(ContactFields.FirstName, 1);
    new DataAccessAdapter().FetchTypedList(fields, table, null);
    DataView toBind = table.DefaultView; 
    listBox1.DataSource = table;
    listBox1.DisplayMember = ContactFields.FirstName.Name;
    textBox2.DataBindings.Add("Text", table, ContactFields.FirstName.Name);

    toBind.ListChanged += collection_ListChanged;
    foreach(DataRowView row in toBind )
      row.PropertyChanged += contact_PropertyChanged;
}

void contact_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
  listBox2.Items.Add("DataRowView.PropertyChanged");
}

void collection_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e)
{
  listBox2.Items.Add("DataView.ListChanged");
}

private void button1_Click(object sender, System.EventArgs e)
{
  //DataView view = (DataView)listBox1.DataSource;
  //view[listBox1.SelectedIndex]["FirstName"] = "Test";
  DataTable table = (DataTable)listBox1.DataSource;
  table.Rows[listBox1.SelectedIndex]["FirstName"] = "Test";
}

This is very strange, because a datatable implements, similar to EntityCollection, IListSource, so it binds through it's default dataview (which I bind in my example above) so it should show exactly the same behavior as the dataview code snippet above. Though it doesn't.

What's strange in the dataview bind example is that it doesn't even raise the propertychanged event, it does that when I click another row. Though in the datatable approach, it does this immediately.

When I bind the collection though a bindingsource, it behaves the same as without a datasource

Now, when I place a breakpoint in EntityBase2.BeginEdit(), It does get called. Simply through the listbox control. So binding to the listbox control implies complex databinding ('complex' is the term MS uses, it's simply a name for defining the databinding type) otherwise BeginEdit wouldn't be called.

I can't intercept calls to the BeginEdit methods on the dataview/table so I can't test if these are called as well, but I doubt they will be called when the datatable is bound but the method IS called when a dataview is bound directly, simply because it postpones the events.

This IMHO implies hardcoded checks on datatable somewhere in the currencymanager, as I don't have any other explanation why our code gets a call to BeginEdit (and thus HAS TO postpone events!) and datatable apparently doesn't but dataview does. (so the datasource type is apparently checked to see if it's a datatable... what else would be the explanation for the fact that when you bind a dataview it simply behaves like the entitycollection...).

Frans Bouma | Lead developer LLBLGen Pro
Nill
User
Posts: 7
Joined: 11-Oct-2006
# Posted on: 14-Dec-2006 13:45:32   

Hmm, very strange and very annoying.

But if the .NET-framework is calling IEditableObject.BeginEdit() on the LLBLGen-objects, I guess there isn't much you can do about it.

I'll choose to accept this as a 'bug' in the .NET-framework.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 14-Dec-2006 13:54:28   

Yeah, I can't do much about it in that case... disappointed I'm sorry it's this way, I understand it's inconvenient..

Frans Bouma | Lead developer LLBLGen Pro