ListChanged is fired too early when FetchEntityCollection with PrefetchPath

Posts   
 
    
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 08-May-2014 23:12:13   

It looks to me, that CurrentChanged and CurrentItemChanged (means ListChanged)events of bindingsource connected to entitycollection is fired too early, before related entities are loaded.

In my scenario I need to set form caption using fields from related entity 1:1, this is done in CurrentItemChanged. And because ListChanged if fired before related entity is fetched, I get always String.Empty

As a workaround I can call myBindingSource.ResetCurrentItem()

Using adapter As New DataAccessAdapter
    adapter.FetchEntityCollection(ecol, Nothing, 0, Nothing, prefetchPath)
End Using
BindingSource.ResetCurrentItem()

I tried another workaround, to load data to tempEC first and then add entities by ecol.Addrange(tempEC) This works also fine, but as I want to use Context this is not the way.

I think ListChanged should be fired after related entities are fetched.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 09-May-2014 08:03:17   

Please tell us more about your workflow. When exactly do you need the info from the related table? and How are you using it? Maybe we can advise you on a better place to put your custom code.

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 09-May-2014 09:42:40   

also be aware that a collection binds through its defaultview (which actually implements IBindingList) and a view has settings to make it update itself always or on demand, see: http://www.llblgen.com/documentation/4.1/LLBLGen%20Pro%20RTF/hh_goto.htm#Using%20the%20generated%20code/Adapter/gencode_usingentityview_adapter.htm#viewbehavior

Frans Bouma | Lead developer LLBLGen Pro
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 09-May-2014 09:49:19   

Hi, I build in Winforms forms for editing master data, means entities. And I change form caption or groupcontrol caption based on data I get from current entity. If I move to another entity or edit and save current one, then caption is always in sync with current entity.

I hook on bindingsource CurrentItemChanged event, when it is fired, then I reread data into caption. Everything works fine, except situation when I need to show in caption data from related entity. When the form is opened and data are loaded by

adapter.FetchEntityCollection(ecol, Nothing, 0, Nothing, prefetchPath)

then CurrentItemChanged is fired 3x times (known fact) but in all three cases related entity is nothing.

It means you fire ListChanged event right after loading entitycollections but before you merge related entities into root EC.

Therefore I have to run BindingSource.ResetCurrentItem() after FetchEntityCollection in order to get fired CurrentItemChanged again. This time it has also merged related entities and caption could read data.

For example say I want to edit Order and I want to see in form caption or form's groupcontrol caption: "CustomerName - OrderNo"

I will fetch Orders into entitycollection with prefetch path to Customers Right after fetching data the event CurrentItemChanged is fired 3x but in all these there cases Order.Customer is Nothing because Customers are not merget into Orders yet.

Therefore something like Caption=Order.Customer.CustomerName & "-" & Order.OrderNo will not work right after the data load. Therefore I have to call BindingSource.ResetCurrentItem() after fetching data

I hope it is more clear to you. Simply said I think you should fire ListChanged AFTER you merge entities with related entities. This way I can avoid calling BindingSource.ResetCurrentItem() after initial load.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 09-May-2014 09:57:23   

What's the origin of the call chain when CurrentItemChanged is raised? I think it's due to the OnPropertyChanged of the FK field, thus something we can't suppress. Did you look at the behavior settings I pointed at?

Frans Bouma | Lead developer LLBLGen Pro
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 09-May-2014 10:43:07   

1/ Yes I read "View behavior on collection changes", thanks.

2/ I am fine to call BindingSource.ResetCurrentItem() after fetching entities with prefetch path if my question or idea is wrong or difficult to study in depth.

Using adapter As New DataAccessAdapter
    adapter.FetchEntityCollection(ecol, Nothing, 0, Nothing, prefetchPath)
End Using
BindingSource.ResetCurrentItem()

If you want to check the call chain here it is:

FlexMARS.BL.dll!FlexMARS.BL.ExBusinessObjectBase.ManagedBindingSource_CurrentItemChanged(Object sender, System.EventArgs e) Line 196 + 0x59 bytes   Basic
System.Windows.Forms.dll!System.Windows.Forms.CurrencyManager.OnCurrentChanged(System.EventArgs e) + 0x12d bytes    
System.Windows.Forms.dll!System.Windows.Forms.CurrencyManager.ChangeRecordState(int newPosition, bool validating, bool endCurrentEdit, bool firePositionChange, bool pullData) + 0x175 bytes    
System.Windows.Forms.dll!System.Windows.Forms.CurrencyManager.List_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e) + 0x230 bytes   
System.Windows.Forms.dll!System.Windows.Forms.BindingSource.InnerList_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e) + 0x45 bytes 
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.EntityViewBase< FlexMARS.DAL.Data.EntityClasses.WorkInstructionRevisionEntity>.OnListChanged(int index, System.ComponentModel.ListChangedType typeOfChange) Line 739  C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.EntityViewBase< FlexMARS.DAL.Data.EntityClasses.WorkInstructionRevisionEntity>._relatedCollectionListChanged(object sender, System.ComponentModel.ListChangedEventArgs e) Line 954    C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.CollectionCore< FlexMARS.DAL.Data.EntityClasses.WorkInstructionRevisionEntity>.OnListChanged(int index, System.ComponentModel.ListChangedType typeOfChange) Line 925  C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.CollectionCore< FlexMARS.DAL.Data.EntityClasses.WorkInstructionRevisionEntity>.SD.LLBLGen.Pro.ORMSupportClasses.IEntityCollectionCoreInternal.CompleteFetchOperation() Line 1517  C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase. ExecuteMultiRowRetrievalQuery(SD.LLBLGen.Pro.ORMSupportClasses.IRetrievalQuery queryToExecute, SD.LLBLGen.Pro.ORMSupportClasses.IEntityFactory2 entityFactory, SD.LLBLGen.Pro.ORMSupportClasses.IEntityCollection2 collectionToFill, SD.LLBLGen.Pro.ORMSupportClasses.IFieldPersistenceInfo[] fieldsPersistenceInfo, bool allowDuplicates, SD.LLBLGen.Pro.ORMSupportClasses.IEntityFields2 fieldsUsedForQuery) Line 617    C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase. FetchEntityCollectionInternal(SD.LLBLGen.Pro.ORMSupportClasses.QueryParameters parameters) Line 4262   C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase. FetchEntityCollection(SD.LLBLGen.Pro.ORMSupportClasses.QueryParameters parameters) Line 2202   C#
FlexMARS.DAL.dll!FlexMARS.DAL.DataAccessAdapterEx.ProvedFetchEntityCollection( SD.LLBLGen.Pro.ORMSupportClasses.QueryParameters parameters) Line 327 + 0xf bytes    Basic
FlexMARS.DAL.dll!FlexMARS.DAL.DataAccessAdapterEx.<closure>.<lambda6>() Line 310 + 0x29 bytes   Basic
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.RecoveryStrategyBase. Execute<bool>(System.Func<bool> toExecute) Line 123 + 0x15 bytes    C#
FlexMARS.DAL.dll!FlexMARS.DAL.DataAccessAdapterEx.FetchEntityCollection( SD.LLBLGen.Pro.ORMSupportClasses.QueryParameters parameters) Line 310 + 0x56 bytes Basic
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase. FetchEntityCollection(SD.LLBLGen.Pro.ORMSupportClasses.IEntityCollection2 collectionToFill, SD.LLBLGen.Pro.ORMSupportClasses.IRelationPredicateBucket filterBucket, int maxNumberOfItemsToReturn, SD.LLBLGen.Pro.ORMSupportClasses.ISortExpression sortClauses, SD.LLBLGen.Pro.ORMSupportClasses.IPrefetchPath2 prefetchPath, SD.LLBLGen.Pro.ORMSupportClasses.ExcludeIncludeFieldsList excludedIncludedFields, int pageNumber, int pageSize) Line 2174    C#
SD.LLBLGen.Pro.ORMSupportClasses.dll!SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase. FetchEntityCollection(SD.LLBLGen.Pro.ORMSupportClasses.IEntityCollection2 collectionToFill, SD.LLBLGen.Pro.ORMSupportClasses.IRelationPredicateBucket filterBucket, int maxNumberOfItemsToReturn, SD.LLBLGen.Pro.ORMSupportClasses.ISortExpression sortClauses, SD.LLBLGen.Pro.ORMSupportClasses.IPrefetchPath2 prefetchPath) Line 2070    C#
FlexMARS.BL.dll!FlexMARS.BL.WorkInstructionRevisionBase.LoadData() Line 139 + 0xa0 bytes    Basic
Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 09-May-2014 13:36:27   

Which LLBLGen runtime library build no. are you using?

Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 09-May-2014 14:51:46   

LLBL Pro v4.1 build 12 March 2014

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 13-May-2014 07:44:45   

Hi Rosacek,

I'm trying to reproduce your scenario. This what I got:

private void Form1_Load(object sender, EventArgs e)
{
    _orders.EntityFactoryToUse = new OrderEntityFactory();
    _txtOrderId.DataBindings.Add("Text", _ordersBindingSource, "OrderId");
    _txtCompanyName.DataBindings.Add("Text", _ordersBindingSource, "CustomerCompanyName");

    var path = new PrefetchPath2((int)EntityType.OrderEntity);
    path.Add(OrderEntity.PrefetchPathCustomer);
    using (var adapter = new DataAccessAdapter())
    {
        adapter.FetchEntityCollection(_orders, null, path);
    }
}

_**CustomerCompanyName **_is a custom property on OrderEntity that simply returns Customer.CompanyName:

public string CustomerCompanyName
{
    get
    {
        var toReturn = string.Empty;
        if (Customer != null)
        {
            toReturn = Customer.CompanyName;
        }

        return toReturn;
    }
}

_ordersBindingSource binds to the _orders EntityCollection. I have a binding navigator. Everything is working, I mean: _txtCompanyName is showing the Customer.CompanyName, except for the first order, but if I go forward and back to the first one, there it is the name. So I think this is what you are experimenting, sort of, right?

David Elizondo | LLBLGen Support Team
Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 13-May-2014 09:54:48   

Yes, seems similar scenario.

I think there are some questions to think of:

1/ Should ListChanged be fired right after entitycollection is filled? Or after also related entities are merged there?

2/ Or should ListChanged be fired after entitycolection is filled and then again after related entities are merged?

3/ Is IList changed when I add related entities? If we think list is just flat table, than NO. But we consider list as a list of any objects, then YES because objects are changed by adding related entities.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 14-May-2014 01:02:01   

3/ Is IList changed when I add related entities? If we think list is just flat table, than NO. But we consider list as a list of any objects, then YES because objects are changed by adding related entities.

Should this be valid, no matter how deep is the level of the added or changed entity?

So for a big connected graph, this might cause a lot of events firing everywhere when one entity is added or changed some where.

Rosacek
User
Posts: 155
Joined: 18-Mar-2012
# Posted on: 14-May-2014 11:41:38   

Just to have it clear, I was talking about firing ListChanged at the end of collection fetching

adapter.FetchEntityCollection(ecol, Nothing, 0, Nothing, prefetchPath)

Not about firing ListChanged everytime anything in graph is changed later on.

The initial question was in ListChanged during FetchEntityCollection should be fired immediatelly after entities are added to collection, or defered to the end of FetchEntityCollection processing, when also related entities are merged.

I do the testing if CurrentItemChanged is fired when I change field in related entity. And the result in NO. It means after changing related entities in graph, we have to fire manualy bindingsource .ResetCurrentItem if we need to reflect such change in databound controls.

Therefore it looks like it is also the answer to my original question: ListChanged is fired right after fetching entities into collection. This is correct behavior, as changes in related entities never fire ListChanged and we always have to keep in mind to use ResetBindings or ResetCurrentItem if we need to refresh controls databound to related fields/entities.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 15-May-2014 01:45:27   

I was answering the following question:

3/ Is IList changed when I add related entities? If we think list is just flat table, than NO. But we consider list as a list of any objects, then YES because objects are changed by adding related entities.