Tallan's Technology Blog

Tallan's Top Technologists Share Their Thoughts on Today's Technology Challenges

Crawling a .netTiers object.

Nick Rubino

A client project uses a .netTiers data access solution.  Some of my recent tasks involved working with the .netTiers layer of the project to perform copying and auditing of the application data.  Out of the box nettiers offers a convention that gives the ability to crawl through the entire structure of a large object graph.  This post describes a generic piece of code I wrote which can perform custom functions on a .netTiers object graph.

All .netTier objects inherit from a base class called EnitityBase which is what gives the ability crawl an object graph. Going through the following EntityCrawler class will give a better understanding of the importance of the EntityBase. Here is the code:

public class EntityCrawler
{
    private readonly List<EntityBase> _touchedEntities;
    private readonly Func<EntityBase, EntityBase> _processEntity;

    public EntityCrawler(Func<EntityBase, EntityBase> customProcess)
    {
        _touchedEntities = new List<EntityBase>();
        _processEntity = customProcess;
    }

    private static bool IsTypeEntityBase(Type t)
    {
        if (t != null)
        {
            return t == typeof(EntityBase) || IsTypeEntityBase(t.BaseType);
        }
        return false;
    }

    public EntityBase CrawlEntity(EntityBase entity)
    {
        if (entity != null && !_touchedEntities.Contains(entity))
        {
            _touchedEntities.Add(entity);

            var entityProperties = entity.GetType().GetProperties().Where(pi =>
                IsTypeEntityBase(pi.PropertyType)
            );

            foreach (var subEntity in entityProperties)
            {
                CrawlEntity((EntityBase)subEntity.GetValue(entity,null));
            }

            var collectionProperties = entity.GetType().GetProperties().Where(pi =>
                pi.PropertyType.IsGenericType
                && pi.PropertyType.GetGenericTypeDefinition() == typeof (TList<>)
                && IsTypeEntityBase(pi.PropertyType.GetGenericArguments()[0])
            );
            foreach (var collectionProperty in collectionProperties)
            {
                foreach (var subEntity in (IList)collectionProperty.GetValue(entity, null))
                {
                    CrawlEntity((EntityBase)subEntity);
                }
            }
            entity = _processEntity(entity);
        }
        return entity;
    }
}

Code Walk-Through

Two private class variables (lines 3-4)_touchedEntities is a list of EntityBases on the object graph that have been hit by the process. Since there may be points in an object graph where you have a circular relationship, it is very important to check that you have already been to that object otherwise you may end up crawling the graph forever.  _processEntity is a function that takes in and EntityBase and spits out an EntityBase.  This is function is what is performed on each entity in the graph and needs to be provided to the constructor.

Constructor (lines 6-10) : Sets the above variables.

IsTypeEntityBase Method (lines 12-19) : Recursively drills through Type t to find if any of its base types are of type entity base.

CrawlEntity Method (lines 21-51) :

  1. Starts off making sure the entity is not null and that it has not been touched yet. If these conditions are true, the entity is added to _touchedEntities and then a list named entityProperties is populated.  EntityProperties contains all the subproperties of type EnityBase on the current entity.  CrawlEntity is then recursively called on each of these entities.
  2. Next a list of named collectionProperties is populated which contains all the sub-properties of the current entity which are of type TList<EntityBase>.  One thing to note, on the last condition “IsTypeEntityBase(pi.PropertyType.GetGenericArguments()[0])”, we know that there is one generic argument because we already know the type is TList<> from the condition before.  Once the collectionProperties are populated, we iterate over each collection and recursively call CrawlEntity on each EntityBase of that collection.
  3. Lastly we call whatever process declared in the constructor on the entity base.

Quick Example

The following code shows a VERY basic example of using the Entity Crawler.  It goes through the entire entity graph and prints each Type and TrackingKey (a pipe separated string containing the primary key for table the entity models and that key’s value) of each entity that has been changed since it was loaded from the database.  This example grabs Product 707 from the Microsoft Adventure Works database (with .netTiers built on top of it), changes a few things on the object and then checks for the changes by crawling the entity.  The function passed into the EntityCrawler also changes the color of any EntityBase of type Product.

private void runMe()
{
    var provider = new SqlProductProvider(CONN_STRING, false, null);
    var product = provider.GetByProductId(707);
    provider.DeepLoad(product,true);

    product.ProductModelIdSource.CatalogDescription = "DESCRIPTION";

    Console.WriteLine(string.Format("The Color before : {0}", product.Color));
    var ec = new EntityCrawler(CheckForChanges);
    ec.CrawlEntity(product);
    Console.WriteLine(string.Format("The Color after : {0}", product.Color));
}

public EntityBase CheckForChanges(EntityBase entity)
{
    if (entity.EntityState == EntityState.Changed)
        Console.WriteLine(string.Format("You changed something on a(n) {0} object with a trackingkey of {1}.",
            entity.GetType().Name, entity.EntityTrackingKey));
    if (entity.GetType() == typeof(Product))
        ((Product) entity).Color = "CHANGED_COLOR";
    return entity;
}

Output:

The Color before : Red
You changed something on a(n) ProductModel object with a trackingkey of ProductModel|33.
The Color after : CHANGED_COLOR

Conclusion

Obviously there is much more you can do than what is exemplified above.  As long as the function you pass into the constructor takes in an EntityBase and spits out an EntityBase it’s fair game.  The main value you will get out of this is to perform actions on or query the data in the target EntityBase that may have been changed since the last time you retrieved a copy of it from your database.

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>