- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
inifinite loop in wpf databinding
Joined: 30-Jun-2005
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
What LLBLGen runtime libraries version do you have?
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?
Joined: 30-Jun-2005
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 I think I was trying to fix the problem by binding to a collection outside of the entity's context or something like that.
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?
Joined: 30-Jun-2005
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)
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
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.