inifinite loop in wpf databinding

Posts   
 
    
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 25-Mar-2009 19:14:53   

Version 2.6 Adapter

I have bound an entity property to a WPF combobox with the following:


<ComboBox Name="ReportWriterComboBox" 
                                  Grid.Column="1" 
                                  Grid.Row="2"
                                  Height="20"
                                  ItemsSource="{Binding Source={StaticResource allReportWritersViewSource}}"
                                  DisplayMemberPath="Name"
                                  SelectedValuePath="Id"
                                  SelectedValue="{Binding Path=ReportWriterId}"
                                  />

The allReportWritersViewSource is a CollectionViewSource with an entitycollection of ReportWriterEntity objects.

When I try and change the selected item of the combobox, I run into an infinite loop where it appears the entity property is being set, but this triggers a chain of events where the databinding system appears to try and set the property again...ad infinitum.

Here is the relevant part of the stack trace. You can see that there is a property set being called at the top and bottom.


>   BentekEnergy.Reports.DataAccess.dll!BentekEnergy.Reports.DataAccess.EntityClasses. ReportMetadataEntity.set_ReportWriterId(Integer? Value = 1) Line 799 + 0xd bytes Basic
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.SetValue(object item, object value) + 0xb1 bytes  
    PresentationFramework.dll!MS.Internal.Data.ClrBindingWorker.UpdateValue(object value) + 0x70 bytes  
    PresentationFramework.dll!System.Windows.Data.BindingExpression.UpdateSource(object value = 1) + 0x80 bytes 
    PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.UpdateValue() + 0x62 bytes  
    PresentationFramework.dll!System.Windows.Data.BindingExpression.Update(bool synchronous) + 0x60 bytes   
    PresentationFramework.dll!System.Windows.Data.BindingExpression.System.Windows. IWeakEventListener.ReceiveWeakEvent(System.Type managerType, object sender, System.EventArgs e) + 0x103 bytes   
    WindowsBase.dll!System.Windows.WeakEventManager.DeliverEventToList(object sender = {System.Windows.Data.BindingListCollectionView}, System.EventArgs args = {System.ComponentModel.CurrentChangingEventArgs}, System.Windows.WeakEventManager.ListenerList list = {System.Windows.WeakEventManager.ListenerList}) + 0x59 bytes  
    WindowsBase.dll!System.Windows.WeakEventManager.DeliverEvent(object sender, System.EventArgs args) + 0xc9 bytes 
    WindowsBase.dll!System.ComponentModel.CurrentChangingEventManager.OnCurrentChanging(object sender, System.ComponentModel.CurrentChangingEventArgs args) + 0xa bytes 
    PresentationFramework.dll!System.Windows.Data.CollectionView.OnCurrentChanging( System.ComponentModel.CurrentChangingEventArgs args) + 0x32 bytes   
    PresentationFramework.dll!System.Windows.Data.CollectionView.OnCurrentChanging() + 0x2d bytes   
    PresentationFramework.dll!System.Windows.Data.BindingListCollectionView.RefreshOverride() + 0xee bytes  
    PresentationFramework.dll!System.Windows.Data.CollectionView.RefreshInternal() + 0x10 bytes 
    PresentationFramework.dll!System.Windows.Data.CollectionView.RefreshOrDefer() + 0x12 bytes  
    PresentationFramework.dll!System.Windows.Data.BindingListCollectionView.OnListChanged(object sender = {SD.LLBLGen.Pro.ORMSupportClasses.EntityView2<BentekEnergy.Reports.DataAccess. EntityClasses.ReportMetadataEntity>}, System.ComponentModel.ListChangedEventArgs args) + 0x372 bytes   
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses. EntityViewBase<BentekEnergy.Reports.DataAccess.EntityClasses.ReportMetadataEntity>.OnListChanged(int index = 0, System.ComponentModel.ListChangedType typeOfChange = Reset) + 0x6c bytes   
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses. EntityViewBase<BentekEnergy.Reports.DataAccess.EntityClasses.ReportMetadataEntity>. HandleRelatedCollectionItemChanged(System.ComponentModel.ListChangedEventArgs eventArguments = {System.ComponentModel.ListChangedEventArgs}, ref bool filterResult = true, ref int indexForEvent = 0, ref System.ComponentModel.ListChangedType changetypeForEvent = ItemChanged, ref bool fireEvent = true) + 0x2d5 bytes 
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses. EntityViewBase<BentekEnergy.Reports.DataAccess.EntityClasses.ReportMetadataEntity>. _relatedCollectionListChanged(object sender = {BentekEnergy.Reports.DataAccess.HelperClasses.EntityCollection<BentekEnergy.Reports. DataAccess.EntityClasses.ReportMetadataEntity>}, System.ComponentModel.ListChangedEventArgs e = {System.ComponentModel.ListChangedEventArgs}) + 0xcb bytes 
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses. CollectionCore<BentekEnergy.Reports.DataAccess.EntityClasses.ReportMetadataEntity>.OnListChanged(int index = 0, System.ComponentModel.ListChangedType typeOfChange = ItemChanged) + 0x72 bytes 
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses. CollectionCore<BentekEnergy.Reports.DataAccess.EntityClasses.ReportMetadataEntity> .OnEntityInListOnEntityContentsChanged(object sender = {BentekEnergy.Reports.DataAccess.EntityClasses.ReportMetadataEntity}, System.EventArgs e = {System.EventArgs}) + 0x74 bytes  
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses. EntityBase2.FlagMeAsChanged() + 0x4f bytes 
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses .EntityBase2.PostFieldValueSetAction(bool fieldValueSet = true, string propertyName = "ReportWriterId") + 0x41 bytes    
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.SetValue(int fieldIndex = 6, object value = 1, bool performDesyncForFKFields = true) + 0x32e bytes  
    SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll!SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.SetValue(int fieldIndex = 6, object value = 1) + 0x2b bytes 
    BentekEnergy.Reports.DataAccess.dll!BentekEnergy.Reports.DataAccess.EntityClasses. ReportMetadataEntity.set_ReportWriterId(Integer? Value = 1) Line 799 + 0x1d bytes    Basic


This **does not** happen to textboxes databound to this same object's properties.

Any ideas?

EDIT: This only seems to happen for the initial setting of the property from value=null. When the property is a non-null value, it can be changed using the combobox with no problem. However, going from null-->[non-null value] causes this issue.

EDIT 2: This only happens on non-new entities. When I manually set the IsNew flag to true, the issue goes away. Set it back to false, the issue comes back. I'm guessing this has something to do with how the entity code treats a changed property value depending on if the entity IsNew flag is set to true or false.

EDIT 3: I can see why the IsNew is affecting what is happening here. There is code in EntityBase2.DetermineIfFieldShouldBeSet that decides that the value "should be set" if the DBValue of the field is null, and the new field is not null (I don't know why this is so, it seems like it should be using ValuesAreEqual(currentvalue, newvalue)). So, anyway, when the entity is not new, a setting of the field value to something that it already is equal to (assuming the DBValue is null) will trigger all the Changed events that get raised to WPF's databinding system. It looks like the databinding system sees these events and tries to re-sync with the entity, but strangely is seems that this is done without polling the databound field's value to see if there "really was" a change in the property...the "new" control value is pushed into the entity, and the process repeats, causing the infinite loop.

EDIT 4: Wow, 4 edits already...I feel proud of myself! Well, this problem does not only happen with Comboboxes, this also happens with TextBoxes. The pattern is: the DbValue=null of a field in entity where IsNew=false...set the field value through a databound control (in WPF) and you end up with a StackOverflowException. The entity may have to also be in a databound collection (not sure about that yet)

EDIT 5: I have made a test application and cannot get this behavior to happen. There is something special going on with my real application, but I have no idea what it is.

EDIT 6: I found how to cause this to happen. Here is how to reproduce: (Using WPF) 1. Add a listview to the window. Set the ItemSource of the ListView to a CollectionViewSource which contains a sort description. This CollectionViewSource should point to the DataContext of the window which you can set to a fetched entitycollection (fetched and set in window_loaded). 2. Add a textbox to the window and bind it to the same CollectionViewSource with a Path that points to a property of your entity. 3. Put an entity into the database which has a null field value (same field that the textbox points to) 4. Run the application and try and set the textbox value (of the entity where this value is null). 5. Watch the application crash with a StackOverflowException.

I could not get this to happen in my test application until I gave the CollectionViewSource a SortDescription. Without a SortDescription, this problem does not occur.

Here is the XAML of my test application:


<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    Title="Window1" Height="300" Width="300">
    
    <Window.Resources>
        <CollectionViewSource x:Key="myViewSource" Source="{Binding}">
            <CollectionViewSource.SortDescriptions>
                <ComponentModel:SortDescription PropertyName="Name"/>
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>

    </Window.Resources>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ListView  ItemsSource="{Binding Source={StaticResource myViewSource}}" IsSynchronizedWithCurrentItem="True">
            <ListView.View>
                <GridView AllowsColumnReorder="False">
                    <GridView.ColumnHeaderContainerStyle>
                        <Style TargetType="GridViewColumnHeader">
                            <Setter Property="Visibility" Value="Collapsed"/>
                        </Style>
                    </GridView.ColumnHeaderContainerStyle>
                
                    <GridViewColumn DisplayMemberBinding="{Binding Path=Name}" />
                </GridView>
            </ListView.View>
        </ListView>
        <TextBox Grid.Column="1" DataContext="{Binding Source={StaticResource myViewSource}}" Text="{Binding Path=StoredProcedurePrefix, UpdateSourceTrigger=PropertyChanged}">
            
        </TextBox>
    </Grid>
</Window>

and here is the code-behind:


Imports DatabindingTest.DatabaseSpecific
Imports DatabindingTest.EntityClasses
Imports DatabindingTest.HelperClasses
Imports DatabindingTest.FactoryClasses
Imports SD.LLBLGen.Pro.ORMSupportClasses

Class Window1

    Private Sub Window1_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        Using daa As New DataAccessAdapter()
        
            Dim collection As New EntityCollection(New ReportMetadataEntityFactory())
            daa.FetchEntityCollection(collection, Nothing)

            Dim newDataContext As New EntityCollection(Of ReportMetadataEntity)
            For Each rw In collection
                newDataContext.Add(rw)
            Next

            Me.DataContext = newDataContext
        End Using
    End Sub
End Class

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 26-Mar-2009 03:48:26   

What LLBLGen runtime libraries version do you have?

David Elizondo | LLBLGen Support Team
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 26-Mar-2009 04:27:28   

OrmSupportClasses version 2.6.9.116

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 26-Mar-2009 13:51:56   

I guess the cause is that the sorting expression is on the field which is displayed as name. I think this happens: you set the value, underlying source gets changed, sort kicks in, re-orders the list, selectedvalue is re-set, and this causes a change.

If you first create an EntityView2 of the collection, and set in the ctor not to update the view after changes, do you see a difference? Also, you could work around this by setting the sortexpression on the entityview2. Of course you've to set the view as the DataContext.

Also, why are you copying over the entities into a new collection after the fetch?

Frans Bouma | Lead developer LLBLGen Pro
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 26-Mar-2009 18:20:49   

Otis wrote:

I guess the cause is that the sorting expression is on the field which is displayed as name. I think this happens: you set the value, underlying source gets changed, sort kicks in, re-orders the list, selectedvalue is re-set, and this causes a change.

If you first create an EntityView2 of the collection, and set in the ctor not to update the view after changes, do you see a difference? Also, you could work around this by setting the sortexpression on the entityview2. Of course you've to set the view as the DataContext.

I'll try this and see if it helps. As an aside, why is the field value set if !entity.IsNew and value IsNot Null and dbValue Is Null? This will cause the field to be set even if it is being set to the exact same value as currentValue (which triggers this mess). Those events firing off after this cause all kinds of binding refreshes and resorting, etc...when it doesn't seem even logical to fire the off CollectionChanged and PropertyChanged events when nothing actually changed.

Otis wrote:

Also, why are you copying over the entities into a new collection after the fetch?

Oh, no good reason simple_smile I think I was trying to fix the problem by binding to a collection outside of the entity's context or something like that.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 26-Mar-2009 20:38:56   

mikeg22 wrote:

Otis wrote:

I guess the cause is that the sorting expression is on the field which is displayed as name. I think this happens: you set the value, underlying source gets changed, sort kicks in, re-orders the list, selectedvalue is re-set, and this causes a change.

If you first create an EntityView2 of the collection, and set in the ctor not to update the view after changes, do you see a difference? Also, you could work around this by setting the sortexpression on the entityview2. Of course you've to set the view as the DataContext.

I'll try this and see if it helps. As an aside, why is the field value set if !entity.IsNew and value IsNot Null and dbValue Is Null? This will cause the field to be set even if it is being set to the exact same value as currentValue (which triggers this mess). Those events firing off after this cause all kinds of binding refreshes and resorting, etc...when it doesn't seem even logical to fire the off CollectionChanged and PropertyChanged events when nothing actually changed.

Hmm, this should not be the case. So just to be clear: - entity is not new - dbvalue is null - value to set the field to is not null - value is equal to currentvalue leads to having the field set again?

Frans Bouma | Lead developer LLBLGen Pro
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 26-Mar-2009 20:44:24   

Otis wrote:

mikeg22 wrote:

Otis wrote:

I guess the cause is that the sorting expression is on the field which is displayed as name. I think this happens: you set the value, underlying source gets changed, sort kicks in, re-orders the list, selectedvalue is re-set, and this causes a change.

If you first create an EntityView2 of the collection, and set in the ctor not to update the view after changes, do you see a difference? Also, you could work around this by setting the sortexpression on the entityview2. Of course you've to set the view as the DataContext.

I'll try this and see if it helps. As an aside, why is the field value set if !entity.IsNew and value IsNot Null and dbValue Is Null? This will cause the field to be set even if it is being set to the exact same value as currentValue (which triggers this mess). Those events firing off after this cause all kinds of binding refreshes and resorting, etc...when it doesn't seem even logical to fire the off CollectionChanged and PropertyChanged events when nothing actually changed.

Hmm, this should not be the case. So just to be clear: - entity is not new - dbvalue is null - value to set the field to is not null - value is equal to currentvalue leads to having the field set again?

Yeah, its in DetermineIfFieldShouldBeSet under the

!entityIsNew &&

...


(fieldToSet.DbValue == null)
&&
(value != null)

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 26-Mar-2009 20:46:14   

Thanks, will look into it, it indeed shouldn't set the field value again causing event mess to happen.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 27-Mar-2009 10:41:38   

Reproduced:


[Test]
public void ChangedEntityFieldSetToSameValueEventRaiseTest()
{
    // events should not be raised if the field is set to the same value if the field is already changed. 
    // fetch order entity
    OrderEntity o = new OrderEntity(10254);
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        Assert.IsTrue(adapter.FetchEntity(o));
    }
    Assert.IsNull(o.Fields["ShipRegion"].CurrentValue);
    int eventsRaisedCount = 0;
    string propertyChangedLast = string.Empty;
    o.PropertyChanged += new PropertyChangedEventHandler(
                        delegate(object sender, PropertyChangedEventArgs e) 
                        {
                            eventsRaisedCount++;
                            propertyChangedLast = e.PropertyName;
                        });

    o.ShipRegion = "Region";
    Assert.IsTrue(o.IsDirty);
    Assert.AreEqual(1, eventsRaisedCount);
    Assert.AreEqual("ShipRegion", propertyChangedLast);
    o.ShipRegion = "Region";
    Assert.AreEqual(1, eventsRaisedCount);    // <<<<<< fails
    Assert.AreEqual("ShipRegion", propertyChangedLast);
}

The assert which fails is 2, not 1 so it proves your idea of what's wrong. Looking into a way to fix this.

(edit) Fixed. See attachment

Frans Bouma | Lead developer LLBLGen Pro
mikeg22
User
Posts: 411
Joined: 30-Jun-2005
# Posted on: 27-Mar-2009 16:43:45   

Great, thanks! Is this going to go into a release version anytime soon?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 27-Mar-2009 17:23:25   

mikeg22 wrote:

Great, thanks! Is this going to go into a release version anytime soon?

Yes, likely on monday. It's a release build, you can use it without problems, all tests pass.

Frans Bouma | Lead developer LLBLGen Pro