Nested Foreach loops bug? or perhpas it is me

Posts   
 
    
hplloyd
User
Posts: 191
Joined: 29-Oct-2004
# Posted on: 05-Oct-2011 14:26:21   

Hi

I am trying to upgrade a set of manager templates I have that I used for V2.6 and now want to use in V3.1. I have taken the opportunity to redevlop all of my manager templates using the TDL language, which I must say is fantastic

Everything works perfectly however when I try to compile my code I get some errors and these seem to stem from having nested <[Foreach RelatedEnitity]> tags. As an example the folowing template extract



<[Foreach RelatedEntity ManyToOne]>
/// <summary>
        /// Retrieves active or all entities including and m:1 entities
        /// </summary>
        /// <param name="includeDeleted">True if deleted entities are to be included in the collection.</param>
        /// <param name="sortToUse">The ISortExpression to use for any processing.</param>
        /// <param name="filter">The <[RelatedEntityName]>Entity to filter by.</param>
        /// <param name="connectionID">The connection to use.</param>
        /// <returns>EntityCollection</returns>
        public static EntityCollection<<[CurrentEntityName]>Entity> MGRGetCollectionWithM1EntitiesFilteredBy<[RelatedEntityName]>Entity(bool includeDeleted, ISortExpression sortToUse, <[RelatedEntityName]>Entity filter, int connectionID)
        {   
            try
            {   
                using (DataAccessAdapter myAdapter=LLBLAdapter.GetNew(connectionID))
                {
                    PrefetchPath2 myPrefetch = new PrefetchPath2( (int) EntityType.<[CurrentEntityName]>Entity );
                    <[Foreach RelatedEntity ManyToOne]>
                    myPrefetch.Add( <[CurrentEntityName]>Entity.PrefetchPath<[RelatedEntityName]> );<[NextForeach]>                 
                    IRelationPredicateBucket myBucket = new RelationPredicateBucket();  
                    IPredicateExpression myExpression = new PredicateExpression();          
                    
                    <[Foreach RelatedEntity ManyToOne]>
                    myBucket.Relations.Add(<[CurrentEntityName]>Entity.Relations.<[RelatedEntityName]>EntityUsing<[RelatedEntityName]>ID);<[NextForeach]>
                    
                    myExpression.Add(PredicateFactory.CompareValue(<[CurrentEntityName]>FieldIndex.<[RelatedEntityName]>ID, ComparisonOperator.Equal, filter.<[RelatedEntityName]>ID));
                    
                    // If active only
                    if (!includeDeleted)
                    {
                        myExpression.AddWithAnd(PredicateFactory.CompareValue(<[CurrentEntityName]>FieldIndex.Deleted, ComparisonOperator.Equal, false));
                    }
                    
                    myBucket.PredicateExpression.Add(myExpression);
                    
                    return FetchCollectionWithPrefetch<<[CurrentEntityName]>Entity>((RelationPredicateBucket) myBucket,sortToUse,myPrefetch,myAdapter);         
                }
            }
            catch (Exception ex)
            {
                throw new Exception ("Unable to retrieve '<[CurrentEntityName]>' for selected '<[RelatedEntityName]>' collection from database with m:1 entities- " + ex.Message);
            }
        }
<[NextForeach]>

You will see that the whole method is run for each Related ManyToOne entity, however within each method we use 2 seperate loops one to generate the Prefetches to add and one to generate the relations to add

The problem is that when the generator gets to the section after the 2 nested loops that looks like this

 myExpression.Add(PredicateFactory.CompareValue(<[CurrentEntityName]>FieldIndex.<[RelatedEntityName]>ID, ComparisonOperator.Equal, filter.<[RelatedEntityName]>ID));

The <[RelatedEntityName]> tag is not replaced with the related entity from the outer loop.

As an example, this is the code that is generated for one of my tables/ entities

/// <summary>
        /// Retrieves active or all entities including and m:1 entities
        /// </summary>
        /// <param name="includeDeleted">True if deleted entities are to be included in the collection.</param>
        /// <param name="sortToUse">The ISortExpression to use for any processing.</param>
        /// <param name="filter">The ServerEntity to filter by.</param>
        /// <param name="connectionID">The connection to use.</param>
        /// <returns>EntityCollection</returns>
        public static EntityCollection<GameEntity> MGRGetCollectionWithM1EntitiesFilteredByServerEntity(bool includeDeleted, ISortExpression sortToUse, ServerEntity filter, int connectionID)
        {   
            try
            {   
                using (DataAccessAdapter myAdapter=LLBLAdapter.GetNew(connectionID))
                {
                    PrefetchPath2 myPrefetch = new PrefetchPath2( (int) EntityType.GameEntity );
                    
                    myPrefetch.Add( GameEntity.PrefetchPathGameType );
                    myPrefetch.Add( GameEntity.PrefetchPathServer );
                    myPrefetch.Add( GameEntity.PrefetchPathUser );
                    
                    IRelationPredicateBucket myBucket = new RelationPredicateBucket();  
                    IPredicateExpression myExpression = new PredicateExpression();          
                    
                    
                    myBucket.Relations.Add(GameEntity.Relations.GameTypeEntityUsingGameTypeID);
                    myBucket.Relations.Add(GameEntity.Relations.ServerEntityUsingServerID);
                    myBucket.Relations.Add(GameEntity.Relations.UserEntityUsingUserID);
                    
                    myExpression.Add(PredicateFactory.CompareValue(GameFieldIndex.ID, ComparisonOperator.Equal, filter.ID));
                    
                    // If active only
                    if (!includeDeleted)
                    {
                        myExpression.AddWithAnd(PredicateFactory.CompareValue(GameFieldIndex.Deleted, ComparisonOperator.Equal, false));
                    }
                    
                    myBucket.PredicateExpression.Add(myExpression);
                    
                    return FetchCollectionWithPrefetch<GameEntity>((RelationPredicateBucket) myBucket,sortToUse,myPrefetch,myAdapter);          
                }
            }
            catch (Exception ex)
            {
                throw new Exception ("Unable to retrieve 'Game' for selected '' collection from database with m:1 entities- " + ex.Message);
            }
        }

The line that is generated as

 myExpression.Add(PredicateFactory.CompareValue(GameFieldIndex.ID, ComparisonOperator.Equal, filter.ID));

Should read

 myExpression.Add(PredicateFactory.CompareValue(GameFieldIndex.ServerID, ComparisonOperator.Equal, filter.ServerID));

I hope this all makes sense - It seems to me that nesting foreach loops in TDL does not work - Am I correct if so how can I use TDL to generate the above script per related entity?

Many thanks in advance

Walaa avatar
Walaa
Support Team
Posts: 14986
Joined: 21-Aug-2005
# Posted on: 05-Oct-2011 16:43:51   

This is copied from the SDK Docs:

The interpreter has a list of 'current elements' which are valid and used in the scope of the current construct, which is a list of references to all kinds of objects which are currently the 'current' for that particular construct. These lists are reset at the beginning of a new template. If you nest two entity loops inside each other, the inner loop will mess with the outer loop's current entity value for example, because both operate on the same current element. This is by design, since the tokens are not defined to form a generic language, but a language dedicated to generate code from entities. Also, if there is no current value (e.g. you start an entity field loop but there is no current entity), the interpreter will skip the loop.

So, you need to use lpt instead. And you can include lpt in a TDL template.

hplloyd
User
Posts: 191
Joined: 29-Oct-2004
# Posted on: 06-Oct-2011 11:08:51   

Ok thanks for that

You mention I can use lpt and TDL in the same file - can you point me in the right direction as to how I do this because the

taskPerformerClass="SD.LLBLGen.Pro.TaskPerformers.CodeEmitter"

which I use to generate the files ignores the lpt and only generates the TDL

If I could combine the 2 this would solve my problem because I could use TDL for the outer loop and lpt for the inner loops

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 07-Oct-2011 05:31:33   

In your .templatebindings file add the .lpt template as includeOnly

...
<templateBinding templateID="ABCD" filename="...my.lpt" templateLanguage="C#" includeOnly="true"/>
...

Then in your TDL template, put a reference where you want to be the lpt output.

<[ ... ]>
<[ ... ]>
...
<# ABCD #> 
David Elizondo | LLBLGen Support Team
hplloyd
User
Posts: 191
Joined: 29-Oct-2004
# Posted on: 08-Feb-2012 11:13:48   

Above you mentioned that I am unable to nest foreach lops in TDL.

Has this changed. I ask because I wanted to get back to fixing my issue and so set up a new test and my manager templates are now working again

Has there been an update to TDL that now allows nested foreach loops?

Many thanks in advance

Walaa avatar
Walaa
Support Team
Posts: 14986
Joined: 21-Aug-2005
# Posted on: 08-Feb-2012 14:51:05   

The status has not changed, and lpt is still the way to go in such case.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39760
Joined: 17-Aug-2003
# Posted on: 09-Feb-2012 12:04:29   

TDL uses a global state. So nesting the same loop inside each other doesnt work. It's a simple language meant for 1 purpose. In general we advice to write .lpt templates instead indeed.

Frans Bouma | Lead developer LLBLGen Pro
hplloyd
User
Posts: 191
Joined: 29-Oct-2004
# Posted on: 10-Feb-2012 15:27:23   

OK so I am trying to use an lpt template inside and TDL template

So far so good. I have adjusted the template binding my .lpt template generates inside the TDL template just where I want it by using the <# ABCD #> as explained above.

I am using _activeObject as the current entity which also works, but what I need to get from this is then the related M:1 entities

I have copied the small lpt template file here - It currently just generates a comment with the information I need. Once I get this working I will make it do what I really want. The code is as follows

// This is current entity - <%=ObjectName%>, Related M1 entities are:
<%foreach(EntityRelation rel in Entity.Relations)
        {
        if((rel.RelationType == EntityRelationType.ManyToOne)  && !rel.UtilizingPropertyIsHidden)
        {
        %>
// <%=rel.RelationEndPoint.Name%> Entity        
<%}}%>

<~

    public string ObjectName
    {
        get
        {
            return GetCurrentEntity().Name;
        }
    }
    
    private EntityDefinition GetCurrentEntity()
    {
        EntityDefinition currentEntity = null;
        if (_activeObject is EntityDefinition)
        {
            currentEntity = (EntityDefinition)_activeObject;
        }
        else
        {
            currentEntity = ((Hashtable)_activeObject)["CurrentEntity"] as EntityDefinition;
        }
            return currentEntity;
    }
    
~>

ObjectName correctly pulls out the name of the current entity. It is the foreach loop I am having difficulty translating from V2.6 to V3 template. How do I need to adjust the above foreach V2.6 code to loop through all the relationships of the current entity and output the reated table name.

Many thanks in advance - Once I understand this then I should have everything I need. I feel a bit out of my depth here !!

hplloyd
User
Posts: 191
Joined: 29-Oct-2004
# Posted on: 10-Feb-2012 15:47:32   

No problem

This seems to work

// This is current entity - <%=ObjectName%>, Related M1 entities are:
<%
var allRelationshipInfosToTraverse = GeneratorUtils.GetAllRelationshipInfosForEntity(_executingGenerator, GetCurrentEntity())
                                                    .Where(ri=>((ri.RelationshipType==EntityRelationshipType.ManyToOne)))
                                                    .ToList();
                                                    
    foreach(var relationshipInfo in allRelationshipInfosToTraverse)
    {
        if(relationshipInfo.NavigatorIsHidden)
        {
            continue;
        }
%>      
// <%=relationshipInfo.RelatedEntity.Name%> Entity      
<%}%>

<~

    public string ObjectName
    {
        get
        {
            return GetCurrentEntity().Name;
        }
    }
    
    private EntityDefinition GetCurrentEntity()
    {
        EntityDefinition currentEntity = null;
        if (_activeObject is EntityDefinition)
        {
            currentEntity = (EntityDefinition)_activeObject;
        }
        else
        {
            currentEntity = ((Hashtable)_activeObject)["CurrentEntity"] as EntityDefinition;
        }
            return currentEntity;
    }
    
    
                                                    
                                                    
    
~>