- Home
- LLBLGen Pro
- Custom Templates
Nested Foreach loops bug? or perhpas it is me
Joined: 29-Oct-2004
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
Joined: 21-Aug-2005
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.
Joined: 29-Oct-2004
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
Joined: 28-Nov-2005
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 #>
Joined: 29-Oct-2004
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
Joined: 17-Aug-2003
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.
Joined: 29-Oct-2004
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 !!
Joined: 29-Oct-2004
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;
}
~>