- Home
- LLBLGen Pro
- Architecture
DTO's and WCF - My Templates Provided
Joined: 05-May-2008
Hi,
When we (our org) was deciding on an SOA strategy and looking at LLBL to create the data tier we had a conundrum. Especially because we wanted to use WCF (via IIS) to deploy them.
We were creating both .Net-based clients (able to use the rich Entities directly) and dumb web service consumming applications, eg. Sharepoint InfoPath Froms Server, which would require DTO's. Unfortunately the dumb consumers couldn't handle the WSDL generated by WCF so we had no choice but to provide DTO's as a minimum.
In the end we decided to provide both and so I scoured the forums and found some DTO templates. So I took them, modified them to make them a little smarter, handle relationships and circular links.
I admit they are not perfect, but they seem to do pretty well. Since I found the originals here on the forums I thought I would post my mods up free for all to use.
Using them should be fairly straight forward but here are the couple of methods to look for 1. DTO Constructor - takes an Entity and copies it (plus related) to a DTO 2. FromEntity - takes an Entity and copies it (plus related) to a DTO 3. ToEntity - takes a DTO and copies it to an Entity, doesn't do any of the related though 4. ToDTOArray - takes an EntityCollection of Entity and converts it to an array of DTO's
The way I tend to use them is to write a service contract for my Entities then duplicate the service contract using DTO's. Then when implementing the DTO service I just implement the methods by converting to/from DTO's and calling the Entity service, eg.
public UserDTO[] GetUsers(bool onlyEnabled, bool withRoles, bool minimal) { return UserDTO.ToDTOArray(UsersService.GetUsers(onlyEnabled, withRoles, minimal)); }
public UserDTO GetUser(Guid id, bool withRoles) { return new UserDTO(UsersService.GetUser(id, withRoles)); }
public UserDTO[] FindUsers(Guid id, string login, string name, bool onlyEnabled, bool withRoles, bool minimal) { return UserDTO.ToDTOArray(UsersService.FindUsers(id, login, name, onlyEnabled, withRoles, minimal)); }
Install Instructions
- Into the *\Templates\SharedTemplates\Net2.x\C# folder copy two files entityDTO.template and entityDTOInterface.template
- Into the *\Templates folder copy the file DTO.templatebindings
- Into the *\Tasks folder copy the file DTO.tasks
- Add the DTO tasks into the generation task queue set in you LLBL project
- In the project add the DTO folder
- Add a reference to System.Runtime.Serialization
- Compile!
Joined: 05-May-2008
entityDTO.template
using System; using System.ComponentModel; using System.Collections; using System.Runtime.Serialization;
using <[RootNamespace]>.HelperClasses; using <[RootNamespace]>.EntityClasses;
using SD.LLBLGen.Pro.ORMSupportClasses;
namespace <[RootNamespace]>.DTOClasses { /// <summary> /// DTO class for the entity '<[CurrentEntityName]>'. /// </summary> [Serializable, DataContract] public <[If UsePartialClasses]>partial <[EndIf]>class <[CurrentEntityName]>DTO : <[ If IsSubType ]><[ SuperTypeName ]>DTO, <[ EndIf]>IDto<<[CurrentEntityName]>Entity> { #region Class Member Declarations <[Foreach EntityField CrLf]> private <[If IsNullable]><[If IsValueType]>Nullable<<[TypeOfField]>><[Else]><[TypeOfField]><[EndIf]><[Else]><[TypeOfField]><[EndIf]> _<[CaseCamel EntityFieldName]>;<[NextForeach]> #endregion
/// <summary>
/// CTor
/// </summary>
public <[CurrentEntityName]>DTO()
{
}
/// <summary>
/// CTor which initializes the DTO with values from its corresponding entity
/// </summary>
/// <param name="entityInstance">The entity instance which holds the values for this DTO</param>
public <[CurrentEntityName]>DTO(<[CurrentEntityName]>Entity entityInstance) : this(entityInstance, new Hashtable(), new Hashtable()) { }
internal <[CurrentEntityName]>DTO(<[CurrentEntityName]>Entity entityInstance, Hashtable seenObjects, Hashtable parents)<[ If IsSubType ]> : base(entityInstance, seenObjects, parents)<[ EndIf]>
{
FromEntity(entityInstance, seenObjects, parents);
}
public virtual IDto<<[CurrentEntityName]>Entity> FromEntity(<[CurrentEntityName]>Entity entityInstance, Hashtable seenObjects, Hashtable parents)
{
<[ If IsSubType ]>base.FromEntity(entityInstance, seenObjects, parents);
<[ EndIf]>seenObjects[entityInstance] = this;
parents = new Hashtable(parents);
parents.Add(entityInstance, null);
<[Foreach EntityField CrLf]> _<[CaseCamel EntityFieldName]> = entityInstance.<[EntityFieldName]>;<[NextForeach]> <[Foreach RelatedEntity OneToMany CrLf]><[If Not MappedFieldRelationIsHidden]> _<[CaseCamel MappedFieldNameRelation]> = RelatedArray<<[RelatedEntityName]>DTO,<[RelatedEntityName]>Entity>(entityInstance.<[MappedFieldNameRelation]>, seenObjects, parents);<[EndIf]><[NextForeach]> <[Foreach RelatedEntity ManyToMany CrLf]><[If Not MappedFieldRelationIsHidden]> _<[CaseCamel MappedFieldNameRelation]> = RelatedArray<<[RelatedEntityName]>DTO,<[RelatedEntityName]>Entity>(entityInstance.<[MappedFieldNameRelation]>, seenObjects, parents);<[EndIf]><[NextForeach]> <[Foreach RelatedEntity ManyToOne CrLf]><[If Not MappedFieldRelationIsHidden]> _<[CaseCamel MappedFieldNameRelation]> = RelatedObject<<[RelatedEntityName]>DTO,<[RelatedEntityName]>Entity>(entityInstance.<[MappedFieldNameRelation]>, seenObjects, parents);//(new <[RelatedEntityName]>DTO(entityInstance.<[MappedFieldNameRelation]>, seenObjects);<[EndIf]><[NextForeach]> <[Foreach RelatedEntity OneToOne CrLf]><[If Not MappedFieldRelationIsHidden]> _<[CaseCamel MappedFieldNameRelation]> = RelatedObject<<[RelatedEntityName]>DTO,<[RelatedEntityName]>Entity>(entityInstance.<[MappedFieldNameRelation]>, seenObjects, parents);<[EndIf]><[NextForeach]>
return this;
}
<[ If Not IsSubType ]>
protected virtual T[] RelatedArray<T,U>(EntityCollection<U> entities, Hashtable seenObjects, Hashtable parents) where T : class, IDto<U>, new() where U : EntityBase2
{
if (null == entities)
{
return null;
}
T[] arr = new T[entities.Count];
int i = 0;
foreach (U entity in entities)
{
if (parents.Contains(entity))
{
return null;
}
}
foreach (U entity in entities)
{
if (seenObjects.Contains(entity))
{
arr[i++] = seenObjects[entity] as T;
}
else
{
arr[i++] = new T().FromEntity(entity, seenObjects, parents) as T;
}
}
return arr;
}
<[ EndIf]>
<[ If Not IsSubType ]>
protected virtual T RelatedObject<T,U>(U entityInstance, Hashtable seenObjects, Hashtable parents) where T : class, IDto<U>, new() where U : EntityBase2
{
if (null == entityInstance)
{
return null;
}
if (seenObjects.Contains(entityInstance))
{
if (parents.Contains(entityInstance))
{
return null;
}
else
{
return seenObjects[entityInstance] as T;
}
}
return new T().FromEntity(entityInstance, seenObjects, parents) as T;
}
<[ EndIf]>
public static <[CurrentEntityName]>DTO[] ToDTOArray(EntityCollection<<[CurrentEntityName]>Entity> entities)
{
Hashtable seenObjects = new Hashtable();
<[CurrentEntityName]>DTO[] arr = new <[CurrentEntityName]>DTO[entities.Count];
for (int i = 0; i < entities.Count; i++)
{
arr[i] = new <[CurrentEntityName]>DTO().FromEntity(entities[i], seenObjects, new Hashtable()) as <[CurrentEntityName]>DTO;
}
return arr;
}
/// <summary>
/// Creates a new entity instance and copies over the values of this DTO
/// </summary>
public <[ If IsSubType ]>override <[SuperTypeName]><[ Else]>virtual <[CurrentEntityName]><[ EndIf]>Entity ToEntity()
{
return ToEntity(new <[CurrentEntityName]>Entity());
}
/// <summary>
/// Copies over the values of this DTO into the entity passed in.
/// </summary>
public virtual <[CurrentEntityName]>Entity ToEntity(<[CurrentEntityName]>Entity toFill)
{
<[Foreach EntityField CrLf]> toFill.<[EntityFieldName]> = _<[CaseCamel EntityFieldName]>;<[NextForeach]> <[ If IsSubType ]> base.ToEntity(toFill);<[ EndIf]> return toFill; }
#region Fields
<[Foreach EntityField CrLf]> /// <summary> The <[EntityFieldName]> property of the Entity <[CurrentEntityName]></summary> [DataMember] public <[If EntityFieldOverrides]>override<[Else]>virtual<[EndIf]> <[If IsNullable]><[If IsValueType]>Nullable<<[TypeOfField]>><[Else]><[TypeOfField]><[EndIf]><[Else]><[TypeOfField]><[EndIf]> <[EntityFieldName]> { get { return _<[CaseCamel EntityFieldName]>;} set { _<[CaseCamel EntityFieldName]> = value; } }<[NextForeach]>
#endregion
#region Relation Fields
<[Foreach RelatedEntity OneToMany CrLf]><[If Not MappedFieldRelationIsHidden]> private <[RelatedEntityName]>DTO[] _<[CaseCamel MappedFieldNameRelation]>;<[EndIf]><[NextForeach]> <[Foreach RelatedEntity ManyToMany CrLf]><[If Not MappedFieldRelationIsHidden]> private <[RelatedEntityName]>DTO[] _<[CaseCamel MappedFieldNameRelation]>;<[EndIf]><[NextForeach]> <[Foreach RelatedEntity ManyToOne CrLf]><[If Not MappedFieldRelationIsHidden]> private <[RelatedEntityName]>DTO _<[CaseCamel MappedFieldNameRelation]>;<[EndIf]><[NextForeach]> <[Foreach RelatedEntity OneToOne CrLf]><[If Not MappedFieldRelationIsHidden]> private <[RelatedEntityName]>DTO _<[CaseCamel MappedFieldNameRelation]>;<[EndIf]><[NextForeach]>
<[Foreach RelatedEntity OneToMany CrLf]><[If Not MappedFieldRelationIsHidden]> /// <summary> Gets the EntityCollection with the related entities of type '<[RelatedEntityName]>Entity' which are related to this entity via a relation of type '1:n'. /// If the EntityCollection hasn't been fetched yet, the collection returned will be empty.</summary> [DataMember] public virtual <[RelatedEntityName]>DTO[] <[MappedFieldNameRelation]> { get { return _<[CaseCamel MappedFieldNameRelation]>; } set { _<[CaseCamel MappedFieldNameRelation]> = value; } }<[EndIf]><[NextForeach]> <[Foreach RelatedEntity ManyToMany CrLf]><[If Not MappedFieldRelationIsHidden]> /// <summary> Gets the EntityCollection with the related entities of type '<[RelatedEntityName]>Entity' which are related to this entity via a relation of type 'm:n'. /// If the EntityCollection hasn't been fetched yet, the collection returned will be empty.</summary> [DataMember] public virtual <[RelatedEntityName]>DTO[] <[MappedFieldNameRelation]> { get { return _<[CaseCamel MappedFieldNameRelation]>; } set { _<[CaseCamel MappedFieldNameRelation]> = value; } }<[EndIf]><[NextForeach]> <[Foreach RelatedEntity ManyToOne CrLf]><[If Not MappedFieldRelationIsHidden]> /// <summary> Gets / sets related entity of type '<[RelatedEntityName]>Entity' which has to be set using a fetch action earlier. If no related entity /// is set for this property, null is returned. This property is not visible in databound grids.</summary> [Browsable(false), DataMember] public virtual <[RelatedEntityName]>DTO <[MappedFieldNameRelation]> { get { return _<[CaseCamel MappedFieldNameRelation]>; } set { _<[CaseCamel MappedFieldNameRelation]> = value; } }<[EndIf]><[NextForeach]> <[Foreach RelatedEntity OneToOne CrLf]><[If Not MappedFieldRelationIsHidden]> /// <summary> Gets / sets related entity of type '<[RelatedEntityName]>Entity' which has to be set using a fetch action earlier. If no related entity /// is set for this property, null is returned. This property is not visible in databound grids.</summary> [Browsable(false), DataMember] public virtual <[RelatedEntityName]>DTO <[MappedFieldNameRelation]> { get { return _<[CaseCamel MappedFieldNameRelation]>; } set { _<[CaseCamel MappedFieldNameRelation]> = value; } }<[EndIf]><[NextForeach]>
#endregion
}
}
Joined: 05-May-2008
entityDTOInterface.template
using System; using System.ComponentModel; using System.Collections; using System.Runtime.Serialization;
using <[RootNamespace]>.HelperClasses; using <[RootNamespace]>.EntityClasses;
using SD.LLBLGen.Pro.ORMSupportClasses;
namespace <[RootNamespace]>.DTOClasses { /// <summary> /// DTO interface. /// </summary> public interface IDto<T> { T ToEntity(T toFill); IDto<T> FromEntity(T entityInstance, Hashtable seenObjects, Hashtable parents); } }
Joined: 05-May-2008
DTO.templatebindings
<?xml version="1.0" encoding="utf-8" ?> - <templateBindings name="DTO Template" description="DTO Template" precedenceLevel="10" databaseDriverID="2D18D138-1DD2-467E-86CC-4838250611AE" xmlns="http://sd/llblgen/pro/templateBindingsDefinition.xsd"> - <supportedPlatforms> <platform name=".NET 2.0" /> </supportedPlatforms> - <language name="C#"> <templateBinding templateID="SD_DTOTemplate" filename="SharedTemplates\Net2.x\C#\entityDTO.template" templateLanguage="TDL" /> <templateBinding templateID="SD_DTOInterfaceTemplate" filename="SharedTemplates\Net2.x\C#\entityDTOInterface.template" templateLanguage="TDL" /> </language> </templateBindings>
Joined: 05-May-2008
DTO.tasks
<?xml version="1.0" ?> - <taskGroup xmlns="http://sd/llblgen/pro/taskGroupElementDefinitions.xsd" name="DTO Templates" isOptional="false" description="General group of tasks which are used DTO templates."> - <supportedPlatforms> <platform name=".NET 2.0" /> </supportedPlatforms> - <supportedTemplateGroups> <templateGroup name="Adapter" /> </supportedTemplateGroups> - <taskGroup name="Create Directories"> - <task name="DTODirectoryCreator" assemblyFilename="SD.LLBLGen.Pro.TaskPerformers.dll" taskPerformerClass="SD.LLBLGen.Pro.TaskPerformers.DirectoryCreator"> - <parameters> <parameter name="folderToCreate" defaultValue="DTO" isOptional="false" description="The folder to create" /> <parameter name="failWhenExistent" defaultValue="false" isOptional="true" description="Flag to signal what to do when the folder already exists. Overrules clearWhenExistent" valueType="boolean" /> <parameter name="clearWhenExistent" defaultValue="false" isOptional="true" description="Flag to signal if an existing folder has to be cleared first. Overruled by failWhenExistent" valueType="boolean" /> </parameters> </task> </taskGroup> - <taskGroup name="Create DTO Classes" description="Create DTO Classes"> - <task name="DTOInterfaceCreator" assemblyFilename="SD.LLBLGen.Pro.TaskPerformers.dll" taskPerformerClass="SD.LLBLGen.Pro.TaskPerformers.CodeEmitter"> - <parameters> <parameter isOptional="false" name="destinationFolder" defaultValue="DTO" /> <parameter isOptional="false" name="failWhenExistent" defaultValue="false" /> <parameter isOptional="false" name="filenameFormat" defaultValue="IDto.cs" /> <parameter isOptional="false" name="templateID" defaultValue="SD_DTOInterfaceTemplate" /> <parameter isOptional="false" name="emitType" defaultValue="generic" /> </parameters> </task> - <task name="DTOClassCreator" assemblyFilename="SD.LLBLGen.Pro.TaskPerformers.dll" taskPerformerClass="SD.LLBLGen.Pro.TaskPerformers.CodeEmitter"> - <parameters> <parameter isOptional="false" name="destinationFolder" defaultValue="DTO" /> <parameter isOptional="false" name="failWhenExistent" defaultValue="false" /> <parameter isOptional="false" name="filenameFormat" defaultValue="[elementName]DTO.[extension]" /> <parameter isOptional="false" name="templateID" defaultValue="SD_DTOTemplate" /> <parameter isOptional="false" name="emitType" defaultValue="allEntities" /> </parameters> </task> </taskGroup> </taskGroup>
Joined: 06-May-2008
Thanks ! I was just thinking about working on these myself, this will save me a ton of time.
In order to get these to work, I had to remove the "databaseDriverID="2D18D138-1DD2-467E-86CC-4838250611AE" from the DTO.templatebindings file in order for LLBLgen to find the templates.
Just a question about how you added these to the project. I usually generate the LLBL projects into a separate folder and reference them in the main project. Do you generate these this way or do you add this folder directly into the main project?
I got a little mixed up at the "add these to the project" part, but other than that they seemed to have worked great.
Joined: 23-Jul-2007
I started looking at these templates too. Nice! Had one question. If I'm to separate the LLBLGen generated DLL from the DTO how do I resolve the issue of the template using the EntityCollection class? It's use implies that I would need to distribute the LLBLGen DLL which is something I'm trying to avoid.
Thanks, -Luke
pablo.manzanera wrote:
Thanks ! I was just thinking about working on these myself, this will save me a ton of time.
In order to get these to work, I had to remove the "databaseDriverID="2D18D138-1DD2-467E-86CC-4838250611AE" from the DTO.templatebindings file in order for LLBLgen to find the templates.
Just a question about how you added these to the project. I usually generate the LLBL projects into a separate folder and reference them in the main project. Do you generate these this way or do you add this folder directly into the main project?
I got a little mixed up at the "add these to the project" part, but other than that they seemed to have worked great.
Joined: 06-May-2008
I haven't gotten to the point of testing the working implementation yet, but if I understand you correctly when you add a reference to a web service that returns DTO or list of DTOs, you will get an associated schema as if you returned a typed dataset.
Also, the results I got after using this:
DAL folder + + - DatabaseGeneric folder + + - DatabaseSpecific folder + + - DTO folder + + - DAL.csproj + + - DALDBSpecific.csproj
--> The .cs files in the DTO folder are in the DTOClasses namespace, and I think they should be in the DTO namespace to match the folder they are in
--> I think it would be good to add a .csproj file generator to the overall template so that it could be referenced just like the DAL and DALDBSpecific projects, and maintained separately from any manually written code
Other than that it looks like the code works as advertised and is very well done.
LukeO wrote:
I started looking at these templates too. Nice! Had one question. If I'm to separate the LLBLGen generated DLL from the DTO how do I resolve the issue of the template using the EntityCollection class? It's use implies that I would need to distribute the LLBLGen DLL which is something I'm trying to avoid.
Thanks, -Luke
Joined: 25-Mar-2006
So I'm a complete noob when it comes to templates etc. I got these to work just fine but I wanted to add a project generator that creates a single C# project (or assembly) for the DTO's. How can this be done. Also, does anyone have an example of how this is ideally used in a wcf app? Thanks for the help!
Joined: 06-May-2008
Seth wrote:
So I'm a complete noob when it comes to templates etc. I got these to work just fine but I wanted to add a project generator that creates a single C# project (or assembly) for the DTO's. How can this be done. Also, does anyone have an example of how this is ideally used in a wcf app? Thanks for the help!
I seperated these out into a project file just by copying some of the template instructions from the ones that come with llblgen, but the references in these classes are setup to be a folder within the main project. Like it is, it won't compile that way.
Can't help you with WCF part, I thought these were supposed to be something different. I thought they were for returning lightweight objects through web services with an associated schema (instead of returning DataSets), but for me they don't return a schema when called through a web service.
Perhaps it's a .NET 3.5 thing, but I couldn't get the desired results when using these to target .NET 2.0 with VS.NET 2008.
Joined: 23-Jul-2007
Seth wrote:
So I'm a complete noob when it comes to templates etc. I got these to work just fine but I wanted to add a project generator that creates a single C# project (or assembly) for the DTO's. How can this be done. Also, does anyone have an example of how this is ideally used in a wcf app? Thanks for the help!
I created a separate project file and had the template dump these into the directory. I then added those newly "existing" files to the project to make my DTO dll.
You'll also need to create another project that uses the DTOs. This is your basic WCF app that instead of returning a DATASET or simple types returns your DTO using the DTO's factory methods. Remember that you'll also need to create a service interface (contract) first.
-Luke
Joined: 06-May-2008
LukeO wrote:
Seth wrote:
So I'm a complete noob when it comes to templates etc. I got these to work just fine but I wanted to add a project generator that creates a single C# project (or assembly) for the DTO's. How can this be done. Also, does anyone have an example of how this is ideally used in a wcf app? Thanks for the help!
I created a separate project file and had the template dump these into the directory. I then added those newly "existing" files to the project to make my DTO dll.
You'll also need to create another project that uses the DTOs. This is your basic WCF app that instead of returning a DATASET or simple types returns your DTO using the DTO's factory methods. Remember that you'll also need to create a service interface (contract) first.
-Luke
What version of the .NET framework are you using?
Joined: 20-Jun-2008
I was wondering what approarch you are using on the service side for deleting orphanded objects from the collections on entity updates / deletes? What approach are you using for switching from the DTO objects to the Entity objects on the service side?
Joined: 23-Jul-2007
3.5
pablo.manzanera wrote:
LukeO wrote:
Seth wrote:
So I'm a complete noob when it comes to templates etc. I got these to work just fine but I wanted to add a project generator that creates a single C# project (or assembly) for the DTO's. How can this be done. Also, does anyone have an example of how this is ideally used in a wcf app? Thanks for the help!
I created a separate project file and had the template dump these into the directory. I then added those newly "existing" files to the project to make my DTO dll.
You'll also need to create another project that uses the DTOs. This is your basic WCF app that instead of returning a DATASET or simple types returns your DTO using the DTO's factory methods. Remember that you'll also need to create a service interface (contract) first.
-Luke
What version of the .NET framework are you using?
Joined: 23-Jul-2007
I'm not trying to make anything too complicated as far as object graphs are concerned. For deletes I tend to (if possible) defer those to the database server using cascading deletes and stored procedures or triggers.
For the DTO -> to Entity objects I'm using the code provided above. Specifically the ToEntity method.
toddaheath wrote:
I was wondering what approarch you are using on the service side for deleting orphanded objects from the collections on entity updates / deletes? What approach are you using for switching from the DTO objects to the Entity objects on the service side?
Joined: 22-Mar-2006
Hi,
Great template ! Question though : How could you make the DTO more "smart" with change-tracking built-in ? So when you hand-over the DTO back to the LLBLGen entity after editing, it then has the possibility to iterate over all properties which have e.g. IsChanged set to True.
Or am I missing something in this DTO architecture / setup ?
grtz, Danny
Joined: 14-Dec-2003
I am starting to work with these templates.
One issue I've found that hasn't been mentioned yet. Read only fields arenot being honored in the ToEntity method. So for example on my entities with TimeStamp fields I get compile errors where the ToEntity method tries to set the timestamp.
For now I am commenting out the assignment. If I find another solution I'll let y'all know.
Joined: 20-Mar-2004
Hi All,
One issue I've found that hasn't been mentioned yet. Read only fields arenot being honored in the ToEntity method. So for example on my entities with TimeStamp fields I get compile errors where the ToEntity method tries to set the timestamp.
Any one has a solution to this?
Thanks.
Kaksss
Joined: 24-Aug-2009
I was following this install instuctions
Install Instructions
- Into the *\Templates\SharedTemplates\Net2.x\C# folder copy two files entityDTO.template and entityDTOInterface.template
- Into the *\Templates folder copy the file DTO.templatebindings
- Into the *\Tasks folder copy the file DTO.tasks
- Add the DTO tasks into the generation task queue set in you LLBL project
- In the project add the DTO folder
- Add a reference to System.Runtime.Serialization
- Compile!
I got stuck at point 4. I cannot see the DTO task in the designer's "Task queue to execute task"
Joined: 27-Aug-2007
I was interrested by this DTO template. However I have a concern regarding the use of the seenObjects hashtable.
I understand the need for it: avoid infinite loops when cycling references exists. However the hashtable identifies the object as unique by its hashcode
The hascode is unique for a given object type. However the seenObjects hastable store entities of different types. therefore unicity of the hascode accross entity types is not garantied anymore....
Anyone has a better solution to this issue? IOW a reference to a good algorithm to perform object structure traversal?
Thanks in advance