Issue with Context

Posts   
 
    
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 07-Sep-2007 18:03:37   

This is what i'm trying to accomplish: 1. Fetch an EntityCollection with only a couple of prefetch paths 2. Modify a field on one of the Primary Entities of the EntityCollection 3. Fetch the modified Primary Entity with additional prefetch paths without changing the dirty primary entity.

  • Is this possible?

To accomplish step 3, i've tried using this method:


            IPrefetchPath2 fetchPrimaryFull= new PrefetchPath2((int)EntityType.PrimaryEntity);
            Context context = new Context();
            context.Add(primaryEntity);

            AddPrimaryEntityPrefetchFull(fetchPrimaryFull); // Adds the Additional Prefetches

            FetchEntity(primaryEntity, fetchPrimaryFull);
            pirmaryEntity= context.Get(primaryEntity) as PrimaryEntity;
            context.Remove(primaryEntity);

The result here is that the after the Fetch, the primary entity is overwritten from what is stored in the database. The primaryEntity within the context is overwritten as well. I thought that adding the dirty entity to the context would allow it to be left alone and not overwritten during the fetch. Is this not the case? Am I not using the context correctly here?

I'm also using LLBLGen 2.0 here, has this changed for 2.5?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 08-Sep-2007 05:39:47   

Hi ZaneZ,

This is how Context works with prefetchPaths:

// C#
DataAccessAdapter adapter = new DataAccessAdapter();
try
{
    Context myContext = new Context();
    CustomerEntity customer = new CustomerEntity("BLONP");
    IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.CustomerEntity);
    prefetchPath.Add(CustomerEntity.PrefetchPathOrders);
    adapter.FetchEntity(customer, prefetchPath, myContext);
    
    // ...
    
    // customer and its orders are now loaded. Say we want to add later on the order details
    // of each order of this customer to this graph. We can do that with the following code.

    // redefine the prefetch path, as if we're somewhere else in the application
    prefetchPath = new PrefetchPath2((int)EntityType.CustomerEntity);
    prefetchPath.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathOrderDetails);

    // fetch the customer again. As it is already added to a Context (myContext), the fetch logic
    // will use that Context object. This fetch action will fetch all data again, but into the same
    // objects and will for each Order entity loaded load the Order Detail entities as well.
    adapter.FetchEntity(customer, prefetchPath);
}
finally
{
    adapter.Dispose();
}

Ref: LLBLGenPro Help - Using generated code - Adapter - Using the Context - Prefetch Path fetches.

David Elizondo | LLBLGen Support Team
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 17-Sep-2007 20:29:48   

Thanks daelmo, I tried exactly that and it is not working. What I'm seeing is that If I change the primary entity after it is initially fetched, after adding it to the context and fetching it, the entity is overwritten with what was in the database. In order to resolve this, I used the following method which seemed to work:

note. that the initial meetingEntity was filled as part of an EntityCollection


public void FillMeetingFull(ref MeetingEntity meetingEntity) 
{
   IPrefetchPath2 fetchMeeting = new PrefetchPath2((int)EntityType.MeetingEntity);
            IRelationPredicateBucket bucket = new RelationPredicateBucket();
            Context context = new Context();
            EntityCollection<MeetingEntity> col = new EntityCollection<MeetingEntity>();

            bucket.PredicateExpression.Add(new PredicateExpression(MeetingFields.MeetingID == meetingEntity.MeetingID));
            col.Add(meetingEntity);
            context.Add(col);

            AddMeetingPrefetchFull(this.RemoteClientInfo.WorkingAsPersonID, fetchMeeting);
            FetchEntityCollection(col, bucket, 0, null, fetchMeeting);

            context.Remove(meeting);
}

There were a couple of problems that came about by doing this.
1. If I prefetched the following: Meeting.MeetingContact.Contact.MeetingContact.Meeting, the primary meeting actually referenced a different object than the Meeting.MeetingContact.Contact.MeetingContact.Meeting entity (See MeetingReferenceIssue.gif). This caused problems when saving the Meeting with its originally fetched EntityCollection because the wrong object was being saved, and not the primary Meeting that was actually Edited.

  1. After removing that prefetch, this method seemed to work again, however there were times where LLBLGen would throw a StackOverflow error on the context.Add(col) method when there was a Large entity graph initially fetched. (See Attached SOSDumpAnalysis.TXT for a stack trace of the error.

From the stack trace, it looks like there are recursive calls through the EntityClasses that are creating the overflow. Each Entity.ActiveContext has a call to the AddInternalsToContext() method that adds a context to each loaded related entity. When walking through this graph by following the stack trace, you will see that the same entities are being recursively called potentially creating the overflow. I'm looking at version 2.0.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 18-Sep-2007 12:53:01   
    col.Add(meetingEntity);
        context.Add(col);

Is the above meetingEntity fetched from the databse or is it a new entity?

  1. If I prefetched the following: Meeting.MeetingContact.Contact.MeetingContact.Meeting, the primary meeting actually referenced a different object than the Meeting.MeetingContact.Contact.MeetingContact.Meeting entity (See MeetingReferenceIssue.gif). This caused problems when saving the Meeting with its originally fetched EntityCollection because the wrong object was being saved, and not the primary Meeting that was actually Edited.

Please post a complete code snippet of this issue, including the prefetchPaths (don't post method calls to formulate the prefetchPath, just post the complete prefetchPath code).

ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 18-Sep-2007 15:26:26   

I am more concerned about the stack overflow problem that I attached the the previous post. I believe that it has to do with the AddInternalsToContext() method on each entity basically walking through a recursive chain of related entities creating a stack overflow. I think that each entity should only have its AddInternalsToContext method called once, similar to how saving entities recursively behaves.

The meetingEntity was previously fetched from this method.


 /// <summary>
        /// Returns an entitycollection of meeting entities with the inputted options filtered out
        /// </summary>
        /// <param name="personID"></param>
        /// <param name="day"></param>
        /// <param name="meetingPersons"></param>
        /// <param name="offices"></param>
        /// <param name="eventTypes"></param>
        /// <param name="sectors"></param>
        /// <param name="securityListings"></param>
        /// <param name="meetingTypeShortName">The type of meeting to return, if null, all meetings and meeting 
        /// requests will be returned</param>
        /// <returns></returns>
        public EntityCollection<MeetingEntity> GetMeetings(int personID, DateTime day, EntityCollection<PersonEntity> persons, EntityCollection<OfficeEntity> offices, EntityCollection<EventTypeEntity> eventTypes, EntityCollection<ClassEntity> sectors, int[] securityListingIDs, string meetingTypeShortName) {
            //EntityCollection meetings = new EntityCollection(new MeetingEntityFactory());
            EntityCollection<MeetingEntity> meetings = new EntityCollection<MeetingEntity>();
            IRelationPredicateBucket bucket = new RelationPredicateBucket();
            IPrefetchPath2 fetchMeetings = new PrefetchPath2((int)EntityType.MeetingEntity);

            bucket.Relations.Add(MeetingEntity.Relations.EventMeetingEntityUsingMeetingID);
            bucket.Relations.Add(EventMeetingEntity.Relations.EventEntityUsingEventID);

            if(day != DateTime.MinValue) {
                bucket.PredicateExpression.Add(MeetingFields.StartDateTime >= day);
            }

            if(persons != null && persons.Count > 0) {
                int[] personIDs = new int[persons.Count];
                int x = 0;
                foreach(PersonEntity p in persons) {
                    personIDs[x++] = p.PersonID;
                }

                bucket.Relations.Add(MeetingEntity.Relations.MeetingPersonEntityUsingMeetingID);
                bucket.Relations.Add(MeetingPersonEntity.Relations.PersonEntityUsingPersonID);
                bucket.Relations.Add(PersonEntity.Relations.PersonOfficeEntityUsingPersonID);

                bucket.PredicateExpression.Add(MeetingPersonFields.PersonID == new ArrayList(personIDs));
            }

            if(offices != null && offices.Count > 0) {
                int[] officeIDs = new int[offices.Count];
                int x = 0;
                foreach(OfficeEntity o in offices) {
                    officeIDs[x++] = o.OfficeID;
                }

                bucket.Relations.Add(EventEntity.Relations.EventOfficeEntityUsingEventID);
                bucket.PredicateExpression.Add(EventOfficeFields.OfficeID == new ArrayList(officeIDs));
            }

            if(eventTypes != null && eventTypes.Count > 0) {
                int[] eventTypeIDs = new int[eventTypes.Count];
                int x = 0;
                foreach(EventTypeEntity et in eventTypes) {
                    eventTypeIDs[x++] = et.EventTypeID;
                }

                bucket.Relations.Add(EventEntity.Relations.EventTypeEntityUsingEventTypeID);
                bucket.PredicateExpression.Add(EventTypeFields.EventTypeID == new ArrayList(eventTypeIDs));
            }

            if(sectors != null && sectors.Count > 0) {
                int[] classIDs = new int[sectors.Count];
                int x = 0;
                foreach(ClassEntity c in sectors) {
                    classIDs[x++] = c.ClassID;
                }
                bucket.Relations.Add(EventEntity.Relations.EventPeriodEntityUsingEventID);
                bucket.Relations.Add(EventPeriodEntity.Relations.EventPeriodClassEntityUsingEventPeriodID);
                bucket.PredicateExpression.Add(EventPeriodClassFields.ClassID == new ArrayList(classIDs));
            }

            if(securityListingIDs != null && securityListingIDs.Length > 0) {
                bucket.Relations.Add(MeetingEntity.Relations.MeetingContactEntityUsingMeetingID);
                bucket.Relations.Add(MeetingContactEntity.Relations.ContactEntityUsingContactID);
                bucket.Relations.Add(ContactEntity.Relations.CompanyAffiliationEntityUsingContactID);
                bucket.Relations.Add(CompanyAffiliationEntity.Relations.CompanyEntityUsingCompanyID);
                bucket.Relations.Add(CompanyEntity.Relations.SecurityEntityUsingCompanyID);
                bucket.Relations.Add(SecurityEntity.Relations.SecurityListingEntityUsingSecurityID);
                bucket.PredicateExpression.Add(SecurityListingFields.SecurityListingID == new ArrayList(securityListingIDs));
            }

            if(meetingTypeShortName != null && meetingTypeShortName.Trim().Length > 0) {
                bucket.Relations.Add(MeetingEntity.Relations.MeetingStatusEntityUsingMeetingStatusID);
                bucket.PredicateExpression.Add(MeetingStatusFields.Name == meetingTypeShortName);
            }

            AddMeetingPrefetchShallow(personID, fetchMeetings, true, true);

            FetchEntityCollection(meetings, bucket, fetchMeetings);

            return meetings;
        }


 private void AddMeetingPrefetchShallow(int personID, IPrefetchPath2 fetchMeeting, bool loadHumans, bool includeRequestsShallow){
            AddMeetingPrefetchShallow(personID, fetchMeeting, loadHumans, includeRequestsShallow, false);
        }
        private void AddMeetingPrefetchShallow(int personID, IPrefetchPath2 fetchMeeting, bool loadHumans, bool includeRequestsShallow, bool loadAddress) {
            // Meeting.MeetingStatus
            PredicateExpression filterMeetingStatus = new PredicateExpression(MeetingStatusFields.IsDeleted == false);

            fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingStatus, 0, filterMeetingStatus);

            if(includeRequestsShallow) {
                // Meeting.MeetingRequest
                PredicateExpression filterMeetingRequest = new PredicateExpression(MeetingMeetingRequestStatusFields.IsDeleted == 0);

                IPrefetchPath2 fetchMeetingRequest = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingMeetingRequestStatus, 0, filterMeetingRequest).SubPath;

                // Meeting.MeetingMeetingRequestStatus.RequestStatus
                PredicateExpression filterMeetingRequestStatus = new PredicateExpression(MeetingRequestStatusFields.IsDeleted == 0);

                fetchMeetingRequest.Add(MeetingMeetingRequestStatusEntity.PrefetchPathMeetingRequestStatus, 0, filterMeetingRequestStatus);

                // Meeting.MeetingLocation
                IPrefetchPath2 fetchMeetingLocation = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingLocation, 0, new PredicateExpression(MeetingLocationFields.IsDeleted == false)).SubPath;

                // Meeting.MeetingLocation.Location
                PredicateExpression filterLocation = new PredicateExpression(LocationFields.IsDeleted == false);
                IPrefetchPath2 fetchLocation = fetchMeetingLocation.Add(MeetingLocationEntity.PrefetchPathLocation, 0, filterLocation).SubPath;
            }

            // Meeting.EventMeeting         
            IPrefetchPath2 fetchEventMeeting = fetchMeeting.Add(MeetingEntity.PrefetchPathEventMeeting).SubPath;

            // Meeting.EventMeeting.Event
            IPrefetchPath2 fetchEvent = fetchEventMeeting.Add(EventMeetingEntity.PrefetchPathEvent).SubPath;

            if(loadHumans) {
                // Meeting.MeetingPerson
                PredicateExpression filterMeetingPerson = new PredicateExpression(MeetingPersonFields.IsDeleted == false);
                IPrefetchPath2 fetchMeetingPerson = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingPerson, 0, filterMeetingPerson).SubPath;

                // Meeting.MeetingContact
                PredicateExpression filterMeetingContact = new PredicateExpression(MeetingContactFields.IsDeleted == false);
                IPrefetchPath2 fetchMeetingContact = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingContact, 0, filterMeetingContact).SubPath;

                // Meeting.MeetingContact.Contact
                IPrefetchPath2 fetchContact = fetchMeetingContact.Add(MeetingContactEntity.PrefetchPathContact).SubPath;

                IPrefetchPath2 fetchContactViewForEvents = fetchContact.Add(ContactEntity.PrefetchPathContactViewForEvents).SubPath;

                if(includeRequestsShallow) {
                    // Meeting.EventMeeting.Event.EventPerson
                    fetchEvent.Add(EventEntity.PrefetchPathEventPerson, 0, new PredicateExpression(EventPersonFields.IsDeleted == false));

                    // Meeting.MeetingContact.Contact.ContactJobFunctionView
                    fetchContact.Add(ContactEntity.PrefetchPathContactJobFunctionView);

                    // Meeting.MeetingContact.Contact.ContactJobTitleView
                    fetchContact.Add(ContactEntity.PrefetchPathContactJobTitleView);

                    // Meeting.MeetingContact.MeetingContactPerson
                    PredicateExpression filterMeetingContactPerson = new PredicateExpression(MeetingContactPersonFields.IsDeleted == false);
                    IPrefetchPath2 fetchMeetingContactPerson = fetchMeetingContact.Add(MeetingContactEntity.PrefetchPathMeetingContactPerson, 0, filterMeetingContactPerson).SubPath;

                    // Meeting.MeetingContact.MeetingContactMeetingAttendeeStatus
                    PredicateExpression filterMeetingContactStatus = new PredicateExpression(MeetingContactMeetingAttendeeStatusFields.IsDeleted == false);
                    SortExpression sortMeetingContactStatus = new SortExpression(MeetingContactMeetingAttendeeStatusFields.CreatedDate | SortOperator.Descending);
                    fetchMeetingContact.Add(MeetingContactEntity.PrefetchPathMeetingContactMeetingAttendeeStatus, 1, filterMeetingContactStatus, null, sortMeetingContactStatus);

                    IPrefetchPath2 fetchCompany = fetchContactViewForEvents.Add(ContactViewForEventsEntity.PrefetchPathCompany).SubPath;
                    CompanyService.AddCompanyPerfetchesForRankAndDesignationFromPerson(personID, fetchCompany);
                    fetchCompany.Add(CompanyEntity.PrefetchPathCompanyAddressView);

                    // Meeting.MeetingPerson.Person
                    IPrefetchPath2 fetchMeetingPersonPerson = fetchMeetingPerson.Add(MeetingPersonEntity.PrefetchPathPerson).SubPath;

                    // Meeting.MeetingPerson.Person.PersonViewForEvents
                    fetchMeetingPersonPerson.Add(PersonEntity.PrefetchPathPersonViewForEvents);

                    // Meeting.MeetingPerson.Person.MeetingPersonMeetingAttendeeStatus
                    fetchMeetingPerson.Add(MeetingPersonEntity.PrefetchPathMeetingPersonMeetingAttendeeStatus);

                    // Meeting.MeetingSecurityListing
                    IPrefetchPath2 fetchMeetingSecurityListing = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingSecurityListing, 0, new PredicateExpression(MeetingSecurityListingFields.IsDeleted == false)).SubPath;
                }

                if(loadAddress){
                    // Meeitng.MeetingLocation
                    IPrefetchPath2 fetchMeetingLocation = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingLocation, 0, new PredicateExpression(MeetingLocationFields.IsDeleted == false)).SubPath;

                    // Meeting.MeetingLocaiton.location
                    fetchMeetingLocation.Add(MeetingLocationEntity.PrefetchPathLocation, 0, new PredicateExpression(LocationFields.IsDeleted == false));
                }
            }
        }


Then one of the meetings that were fetched from the previous method are fully loaded later when the user opens up a meeting. When the meetingEntity is added to the context, the error occurs.


IPrefetchPath2 fetchMeeting = new PrefetchPath2((int)EntityType.MeetingEntity);
            IRelationPredicateBucket bucket = new RelationPredicateBucket();
            Context context = new Context();
            EntityCollection<MeetingEntity> col = new EntityCollection<MeetingEntity>();

            bucket.PredicateExpression.Add(new PredicateExpression(MeetingFields.MeetingID == meeting.MeetingID));
            col.Add(meeting);
            context.Add(col);

            AddMeetingPrefetchFull(this.RemoteClientInfo.WorkingAsPersonID, fetchMeeting);
            FetchEntityCollection(col, bucket, 0, null, fetchMeeting);

            context.Remove(meeting);
          }


  /// <summary>
        /// Adds Meeting Prefetch Paths that will fully load a Meeting
        /// </summary>
        /// <param name="ownerID"></param>
        /// <param name="fetchMeeting"></param>
        private void AddMeetingPrefetchFull(int ownerID, IPrefetchPath2 fetchMeeting) {
            IPrefetchPath2 fetchMeetingType = null;
            AddMeetingPrefetchFull(ownerID, fetchMeeting, ref fetchMeetingType, false);
        }

        private void AddMeetingPrefetchFull(int ownerID, IPrefetchPath2 fetchMeeting, bool addContactMeetingPersonPerson) {
            IPrefetchPath2 fetchMeetingType = null;
            AddMeetingPrefetchFull(ownerID, fetchMeeting, ref fetchMeetingType, addContactMeetingPersonPerson);
        }

        private void AddMeetingPrefetchFull(int ownerID, IPrefetchPath2 fetchMeeting, ref IPrefetchPath2 fetchMeetingType, bool addContactMeetingPersonPerson) {

            // Meeting.MeetingType, sorted by most recently used
            PredicateExpression filterMeetingType = new PredicateExpression(MeetingTypeFields.IsDeleted == false);
            fetchMeetingType = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingType, 0, filterMeetingType).SubPath;

            // Meeting.MeetingStatus
            PredicateExpression filterMeetingStatus = new PredicateExpression(MeetingStatusFields.IsDeleted == false);
            fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingStatus, 0, filterMeetingStatus);

            // Meeting.MeetingLocation
            PredicateExpression filterMeetingLocation = new PredicateExpression(MeetingLocationFields.IsDeleted == false);

            IPrefetchPath2 fetchMeetingLocation = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingLocation, 0, filterMeetingLocation).SubPath;

            // Meeting.MeetingLocation.Location
            PredicateExpression filterLocation = new PredicateExpression(LocationFields.IsDeleted == false);

            IPrefetchPath2 fetchLocation = fetchMeetingLocation.Add(MeetingLocationEntity.PrefetchPathLocation, 0, filterLocation).SubPath;

            // Meeting.MeetingLocation.Location.OfficeAddress
            PredicateExpression filterOfficeAddress = new PredicateExpression(OfficeAddressFields.IsDeleted == false);

            IPrefetchPath2 fetchOfficeAddress = fetchLocation.Add(LocationEntity.PrefetchPathOfficeAddress, 0, filterOfficeAddress).SubPath;

            // Meeting.MeetingLocation.Location.Location.OfficeAddress.Office
            IPrefetchPath2 fetchOffice = fetchOfficeAddress.Add(OfficeAddressEntity.PrefetchPathOffice).SubPath;

            // Meeting.MeetingLocation.Location.Location.OfficeAddress.Office.Company
            fetchOffice.Add(OfficeEntity.PrefetchPathCompany);

            // Meeting.MeetingLocation.Location.Resource
            // Show deleted items, so that if they are deleted they are still visible
            fetchLocation.Add(LocationEntity.PrefetchPathResource);//, 0, filterLocationResource);

            // Meeting.MeetingLocation.Location.Location.OfficeAddress.Address
            IPrefetchPath2 fetchAddress = fetchOfficeAddress.Add(OfficeAddressEntity.PrefetchPathAddress).SubPath;
            PredicateExpression filterResource = new PredicateExpression(ResourceFields.IsDeleted == false);

            fetchAddress.Add(AddressEntity.PrefetchPathResource, 0, filterResource);

            // Meeting.MeetingContact
            PredicateExpression filterMeetingContact = new PredicateExpression(MeetingContactFields.IsDeleted == false);

            IPrefetchPath2 fetchMeetingContact = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingContact, 0, filterMeetingContact).SubPath;

            // Meeting.MeetingContact.MeetingContactMeetingContactStatus
            PredicateExpression filterMeetingContactStatus = new PredicateExpression(MeetingContactMeetingAttendeeStatusFields.IsDeleted == false);
            fetchMeetingContact.Add(MeetingContactEntity.PrefetchPathMeetingContactMeetingAttendeeStatus, 0, filterMeetingContactStatus);

            // Meeting.MeetingContact.Contact
            IPrefetchPath2 fetchContact = fetchMeetingContact.Add(MeetingContactEntity.PrefetchPathContact).SubPath;
            fetchContact.Add(ContactEntity.PrefetchPathContactJobFunctionView);
            fetchContact.Add(ContactEntity.PrefetchPathContactJobTitleView);

            // Meeting.MeetingContact.MeetingContactPerson
            PredicateExpression filterMeetingContactPerson = new PredicateExpression(MeetingContactPersonFields.IsDeleted == false);

            IPrefetchPath2 fetchMeetingContactPerson = fetchMeetingContact.Add(MeetingContactEntity.PrefetchPathMeetingContactPerson, 0, filterMeetingContactPerson).SubPath;

            // Meeting.MeetingContact.MeetingContactPerson.Person
            PredicateExpression filterPerson = new PredicateExpression(PersonFields.IsDeleted == false);
            IPrefetchPath2 fetchPersonEvents = fetchMeetingContactPerson.Add(MeetingContactPersonEntity.PrefetchPathPerson, 0, filterPerson).SubPath;
            //MeetingContactEntity.PrefetchPathMeetingContactPerson.SubPath.Add(MeetingContactPersonEntity.PrefetchPathPerson, 0, filterPerson);

            if(addContactMeetingPersonPerson) {
                fetchPersonEvents.Add(PersonEntity.PrefetchPathPersonViewForEvents);
            }
//**** THIS IS THE PREFETCH THAT HAD TO BE REMOVED BECAUSE IT WAS CREATING THE OBJECT MISMATCH BETWEEN THE SAME MEETING ENTITIES SEEN FROM THE ATTACHED DEBUG SCREEN SHOT ****/
  // Add 2 of the previous meeting locations to each contact
            // Contact.MeetingContact
            SortExpression sort = new SortExpression(MeetingContactFields.CreatedDate | SortOperator.Descending);
            PredicateExpression filterMeetingContact = new PredicateExpression(MeetingContactFields.IsDeleted == false);
            IRelationCollection relationMeetingContact = new RelationCollection();
relationMeetingContact.Add(MeetingContactEntity.Relations.MeetingEntityUsingMeetingID);
relationMeetingContact.Add(MeetingEntity.Relations.MeetingLocationEntityUsingMeetingID);
relationMeetingContact.Add(MeetingLocationEntity.Relations.LocationEntityUsingLocationID);
relationMeetingContact.Add(LocationEntity.Relations.OfficeAddressEntityUsingOfficeAddressID);
            IPrefetchPath2 fetchMeetingContact = fetchContact.Add(ContactEntity.PrefetchPathMeetingContact, 2, filterMeetingContact, relationMeetingContact, sort).SubPath;



            // Meeting.MeetingPerson
            PredicateExpression filterMeetingPerson = new PredicateExpression(MeetingPersonFields.IsDeleted == false);
            IPrefetchPath2 fetchMeetingPerson = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingPerson, 0, filterMeetingPerson).SubPath;

            // Meeting.MeetingPerson.MeetingPersonMeetingAttendeeStatus
            PredicateExpression filterMeetingPersonStatus = new PredicateExpression(MeetingPersonMeetingAttendeeStatusFields.IsDeleted == false);
            SortExpression sortMeetingPersonStatus = new SortExpression(MeetingPersonMeetingAttendeeStatusFields.CreatedDate | SortOperator.Descending);
            fetchMeetingPerson.Add(MeetingPersonEntity.PrefetchPathMeetingPersonMeetingAttendeeStatus, 1, filterMeetingPersonStatus, null, sortMeetingPersonStatus);

            // Meeting.MeetingPerson.Person
            IPrefetchPath2 fetchPerson = fetchMeetingPerson.Add(MeetingPersonEntity.PrefetchPathPerson, 0, filterPerson).SubPath;

            MeetingService.AddPersonPrefetch(fetchPerson);

            // Meeting.MeetingTimeDescriptor
            // Show deleted items, so that if they are deleted they are still visible
            fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingTimeDescriptor);//, 0, filterMeetingTimeDesciptor);


            // Meeting.MeetingRequest
            PredicateExpression filterMeetingRequest = new PredicateExpression(MeetingMeetingRequestStatusFields.IsDeleted == false);
            IPrefetchPath2 fetchMeetingRequest = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingMeetingRequestStatus, 0, filterMeetingRequest).SubPath;

            // Meeting.MeetingMeetingRequestStatus.RequestStatus
            PredicateExpression filterMeetingRequestStatus = new PredicateExpression(MeetingRequestStatusFields.IsDeleted == false);
            fetchMeetingRequest.Add(MeetingMeetingRequestStatusEntity.PrefetchPathMeetingRequestStatus, 0, filterMeetingRequestStatus);

            // Meeting.EventMeeting         
            IPrefetchPath2 fetchEventMeeting = fetchMeeting.Add(MeetingEntity.PrefetchPathEventMeeting).SubPath;

            // Meeting.EventMeeting.Event
            fetchEventMeeting.Add(EventMeetingEntity.PrefetchPathEvent);

            // Meeting.MeetingSecurityListing
            IPrefetchPath2 fetchMeetingSecurityListing = fetchMeeting.Add(MeetingEntity.PrefetchPathMeetingSecurityListing, 0, new PredicateExpression(MeetingSecurityListingFields.IsDeleted == false)).SubPath;
        }

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 19-Sep-2007 11:32:46   

What's the build number of the runtime libraries and the designer? (see: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7717 )

Recursion stops properly in my tests so it might be you've an outdated runtime lib version or templateset.

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 19-Sep-2007 16:13:50   

2.0.0.0 Final Febuary 14th 2007

SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll: 2.0.07.0219

I'm using the Adaptor on .Net 2.0

SQL Server 2005

Here is the Generated code for the MeetingEntity that appears to be causing the stack overflow. It looks like the overflow will occur depending on how many entities are attached to the graph and how much is prefetched.

We have worked around this problem by not using the context in our application. I'm just trying to make you aware that there may be a problem here. The Recusion actually does stop for us too if you go to the end of the stack trace I attached in the previous thread, however it throws the error when it tries to add the entity to the dictionary, and I believe the recursive calls prior to it are creating the problem.

We haven't moved to 2.5 yet, but if you have seen this as a problem and it if fixed in 2.5 that would be good to know. Thanks.


    /// <summary> Adds the internals to the active context. </summary>
        protected override void AddInternalsToContext()
        {
            if(_customPropertyValue!=null)
            {
                _customPropertyValue.ActiveContext = base.ActiveContext;
            }
            if(_eventMeeting!=null)
            {
                _eventMeeting.ActiveContext = base.ActiveContext;
            }
            if(_meetingContact!=null)
            {
                _meetingContact.ActiveContext = base.ActiveContext;
            }
            if(_meetingLocation!=null)
            {
                _meetingLocation.ActiveContext = base.ActiveContext;
            }
            if(_meetingMeetingRequestStatus!=null)
            {
                _meetingMeetingRequestStatus.ActiveContext = base.ActiveContext;
            }
            if(_meetingPerson!=null)
            {
                _meetingPerson.ActiveContext = base.ActiveContext;
            }
            if(_meetingSecurityListing!=null)
            {
                _meetingSecurityListing.ActiveContext = base.ActiveContext;
            }
            if(_message!=null)
            {
                _message.ActiveContext = base.ActiveContext;
            }
            if(_nSEntityFinancialValue!=null)
            {
                _nSEntityFinancialValue.ActiveContext = base.ActiveContext;
            }
            if(_nSEntityPhone!=null)
            {
                _nSEntityPhone.ActiveContext = base.ActiveContext;
            }

            if(_meetingStatus!=null)
            {
                _meetingStatus.ActiveContext = base.ActiveContext;
            }
            if(_meetingTimeDescriptor!=null)
            {
                _meetingTimeDescriptor.ActiveContext = base.ActiveContext;
            }
            if(_meetingType!=null)
            {
                _meetingType.ActiveContext = base.ActiveContext;
            }

        }


Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 20-Sep-2007 11:48:11   

I picked this post, because I think it will give you some pointers what might be wrong. The context uses the PK for identifying entity instances (i.e. the data INSIDE entity class instances). If you change a PK value, it can't find back an already fetched entity in the context, so this could lead to unexpected results. In general: don't change PK values. It's of no use really: changing a PK comes down to removing the entity and re-adding it as new.

If you really must, you can, but keep it out of the context.

[quote]

ZaneZ wrote:

Thanks daelmo, I tried exactly that and it is not working. What I'm seeing is that If I change the primary entity after it is initially fetched, after adding it to the context and fetching it, the entity is overwritten with what was in the database. In order to resolve this, I used the following method which seemed to work:

note. that the initial meetingEntity was filled as part of an EntityCollection


public void FillMeetingFull(ref MeetingEntity meetingEntity) 
{
   IPrefetchPath2 fetchMeeting = new PrefetchPath2((int)EntityType.MeetingEntity);
            IRelationPredicateBucket bucket = new RelationPredicateBucket();
            Context context = new Context();
            EntityCollection<MeetingEntity> col = new EntityCollection<MeetingEntity>();

            bucket.PredicateExpression.Add(new PredicateExpression(MeetingFields.MeetingID == meetingEntity.MeetingID));
            col.Add(meetingEntity);
            context.Add(col);

            AddMeetingPrefetchFull(this.RemoteClientInfo.WorkingAsPersonID, fetchMeeting);
            FetchEntityCollection(col, bucket, 0, null, fetchMeeting);

            context.Remove(meeting);
}

There were a couple of problems that came about by doing this.
1. If I prefetched the following: Meeting.MeetingContact.Contact.MeetingContact.Meeting, the primary meeting actually referenced a different object than the Meeting.MeetingContact.Contact.MeetingContact.Meeting entity (See MeetingReferenceIssue.gif). This caused problems when saving the Meeting with its originally fetched EntityCollection because the wrong object was being saved, and not the primary Meeting that was actually Edited.

The path: Meeting.MeetingContact.Contact.MeetingContact.Meeting fetches MeetingContact twice. This is unnecessary: MeetingContact.Contact already assigns MeetingContact to Contact and vice versa, so you don't have to do that again. You should fetch: Meeting.MeetingContact.Contact

That's it. You don't have to loop the path back, LLBLGen Pro takes care of that for you: myCustomer.Orders.Add(myOrder); will also do for you: myOrder.Customer = myCustomer;

and vice versa: myOrder.Customer = myCustomer; will do: myCustomer.Orders.Add(myOrder);

(it avoids recursion, don't worry)

  1. After removing that prefetch, this method seemed to work again, however there were times where LLBLGen would throw a StackOverflow error on the context.Add(col) method when there was a Large entity graph initially fetched. (See Attached SOSDumpAnalysis.TXT for a stack trace of the error.

From the stack trace, it looks like there are recursive calls through the EntityClasses that are creating the overflow. Each Entity.ActiveContext has a call to the AddInternalsToContext() method that adds a context to each loaded related entity. When walking through this graph by following the stack trace, you will see that the same entities are being recursively called potentially creating the overflow. I'm looking at version 2.0.

I think the problem isn't that there's a stack overflow, I think the recursion simply went too deep, because the graph is so deep due to the seemingly endless path of different objects because of the loop back in your prefetch path.

MeetingContact.ActiveContext will set itself IF its own context variable is null and the value to set isn't null OR its context variable isn't null and the value is null. In all other cases, there's nothing done and no recursive call is made. As its context variable is null, it is set, and it then will set its internals as well. This is Contact. Contact will do the same, and will set its internals as well, which is Meeting. Meeting should have the same contact referenced as it is referenced by, but the loop in the path has fetched a different OBJECT. As the fetch takes place FIRST, before it is added to the context, it isn't the same object and that meeting's Contact object is different. As it's different, its context variable is null and the whole circus starts again... simple_smile

I'm pretty sure that the non-circular path will make these problems go away. You never have to fetch a node again in the path, that's done for you.

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 23-Sep-2007 20:13:02   

Thanks for your time. I would like to keep the 2 issues that i'm having separate because I don't think they are related.

Issue #1 Stack Overflow:

I think the problem isn't that there's a stack overflow, I think the recursion simply went too deep, because the graph is so deep due to the seemingly endless path of different objects because of the loop back in your prefetch path.

I initially thought that this was the problem, however the stack overflow was occurring within the Context.Add method, before the "Full Meeting Prefetch" was fetched. So it was actually causing the error on the meeting that was tied to a collection that was "shallow" loaded with prefetches that did not have the loop back to re-fetching Meetings. So it looks like the context has limits to how many entities are in the graph.

Issue #2, different objects of the same entity in a graph: This was definitely caused by adding the loop back prefetch path. I have removed this extra prefetch, and this error has been resolved.

If you change a PK value, it can't find back an already fetched entity in the context, so this could lead to unexpected results. In general: don't change PK values. It's of no use really: changing a PK comes down to removing the entity and re-adding it as new.

I definitely never changed a PK value, so this shouldn't be the cause of the problem.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 24-Sep-2007 10:54:56   

ZaneZ wrote:

Thanks for your time. I would like to keep the 2 issues that i'm having separate because I don't think they are related.

Issue #1 Stack Overflow:

I think the problem isn't that there's a stack overflow, I think the recursion simply went too deep, because the graph is so deep due to the seemingly endless path of different objects because of the loop back in your prefetch path.

I initially thought that this was the problem, however the stack overflow was occurring within the Context.Add method, before the "Full Meeting Prefetch" was fetched. So it was actually causing the error on the meeting that was tied to a collection that was "shallow" loaded with prefetches that did not have the loop back to re-fetching Meetings. So it looks like the context has limits to how many entities are in the graph.

It simply sets ActiveContext of the entity added to the instance of the context. From there, the graph gets its ActiveContext property set to the context instance recursively if they're not already in a context. This is necessary to work with these entities with the same context however they're not actually added to the context because removing them afterwards from the context shouldn't be necessary however if everything is actually added, it has to.

So this recursion could have a rather long path as it follows a path through the graph, but it can't do a node 'twice' because it only sets referenced entities' ActiveContext object to the context instance if its own ActiveContext object is set to a value and it wasn't set before. So a node is only touched once.

Therefore I find it odd that the graph recursion causes this problem, because saving a graph will also traverse through the graph and that doesn't cause any problem....

I looked at the code again and I can't find a problematic issue there...

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 25-Sep-2007 16:06:12   

Can you take a quick look at the stack dump from my 2nd post on this thread? It shows that the recursiveness of the setting the context between related entities causes the stack overflow. From looking at the addresses of some of the Meeting Entitites, it appeared that it was going through it's AddInternalsToContext() Method multiple times for the same MeetingEntities which should not be possible.

So this recursion could have a rather long path as it follows a path through the graph, but it can't do a node 'twice' because it only sets referenced entities' ActiveContext object to the context instance if its own ActiveContext object is set to a value and it wasn't set before. So a node is only touched once.

Ok, I see what you are saying:


 public Context ActiveContext {
            get {
                return _activeContext;
            }
            set {
                if(((_activeContext == null) && (value != null)) || ((_activeContext != null) && (value == null))) {
                    _activeContext = value;
                    AddInternalsToContext();
                }
            }
        }

This basically means that the only reason for the stack overflow is the shear size of the graph that was being added to the context? Thanks again for your help, it is much appreciated!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Sep-2007 11:08:55   

ZaneZ wrote:

Can you take a quick look at the stack dump from my 2nd post on this thread? It shows that the recursiveness of the setting the context between related entities causes the stack overflow. From looking at the addresses of some of the Meeting Entitites, it appeared that it was going through it's AddInternalsToContext() Method multiple times for the same MeetingEntities which should not be possible.

Correct, though it's hard to see if these were the same meeting entities or different ones (entity objects, so same data, different object).

What I could see is that they're all different.

So this recursion could have a rather long path as it follows a path through the graph, but it can't do a node 'twice' because it only sets referenced entities' ActiveContext object to the context instance if its own ActiveContext object is set to a value and it wasn't set before. So a node is only touched once.

Ok, I see what you are saying:


 public Context ActiveContext {
            get {
                return _activeContext;
            }
            set {
                if(((_activeContext == null) && (value != null)) || ((_activeContext != null) && (value == null))) {
                    _activeContext = value;
                    AddInternalsToContext();
                }
            }
        }

This basically means that the only reason for the stack overflow is the shear size of the graph that was being added to the context? Thanks again for your help, it is much appreciated! It looks like it, but that's of course not really helpful. I'll try if I can fetch a really large graph with a context to see if I can reproduce this and then check if I can fix this.

Frans Bouma | Lead developer LLBLGen Pro
ZaneZ
User
Posts: 31
Joined: 21-Dec-2004
# Posted on: 26-Sep-2007 18:36:46   

Thanks again! Let me know if you find anything.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 27-Sep-2007 12:44:29   

I fetched 17000 message entities with all threads, all forums and all sections + all users in an m:n relation into a single graph. Then I added it to a context. Worked OK.

Then I tried to fetch it within a context directly. Worked OK as well... I don't know what could have caused this though... (v2.5)

Frans Bouma | Lead developer LLBLGen Pro
acradyn
User
Posts: 57
Joined: 03-Apr-2004
# Posted on: 27-Sep-2007 14:37:33   

I could be wrong as I haven't walked through it actually during debug time but just looking at the function it appears the Stack is a function of the number of Entities in the main EntityCollection * the NUMBER and DEPTH of Prefetches * the prefetch's object graphs that are linked to eachother.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 28-Sep-2007 10:49:03   

acradyn wrote:

I could be wrong as I haven't walked through it actually during debug time but just looking at the function it appears the Stack is a function of the number of Entities in the main EntityCollection * the NUMBER and DEPTH of Prefetches * the prefetch's object graphs that are linked to eachother.

Depends. The example I used isn't a good one I think, as it's different from the model which caused the error. The main problem is the length of the path through the graph. Every node hop is a new stackframe on the stack, and if the path is long enough, the stack will overflow.

So, the routine isn't diving into recursion per graph level, but per instance reference. If you use a graph with a direction downwards (like I used: Section -> Forum -> Thread -> Message), you won't run into directions upwards (message in 2 forums for example). This means that teh depth of recursion is 4 at max.

If you have a graph where indirection between graph levels is common, you will have a long path. For example Parent and Child. You have 1000 parents and 4000 children. Even child has multiple parents, and a parent can have one or more children. If I then start with Parent X and traverse to CHild Xa, I then move up to parent Y from Xa. Y has besides Xa also child Ya. Ya has also parent Z. Z has besides child Ya also Za etc. etc. This is a loooooong path if there are a lot of entities.

This problem doesn't occur if the ActiveContext property of the container to fetch into (collection) is set to a context and THEN the container is fetched. Also, adding thousands and thousands of entities to a context isn't really helpful, the context is meant to provide uniquing in a semantical context. Adding a lot of entities to it doesn't really mean anything if you're not needing them to be unique instances. However during a fetch you might want to have them as unique instances if the entity is fetched more than once, like in an m:n relation and also in a normal relation.

Frans Bouma | Lead developer LLBLGen Pro