Geeks With Blogs

News



Add to Google

Tim Hibbard CEO for EnGraph software

When we decided that ParaPlan 4.0 would be rebuilt in .NET (from Access), we decided not to redesign the SQL database.  Even though we have some inefficiencies in our db we want to decrease development time and allow our beta testers to be able to switch back and forth between our Access front end and our .NET front end.

As development began, we decided that we needed an object for each table and that we could eliminate some of the database inefficiencies in our objects.  So we have an Appointment object that represents our Appointments table, but where the Appointment table has Address1, Address2, City, State, etc, we would create a Location object that we could reuse everywhere we talked about locations.  As another example, we created a Client object that represented the Clients table, but we would use the Client object in other tables.  So instead of Appointment.ClientID, Appointment.ClientFirstName, we would have Appointment.Client.ID and Appointment.Client.Name.FirstName, etc ,you get the point.

I had a good talk with Dave Donaldson at HDC 2006 about removing functionality from entity objects and letting them just hold data.  I then created EntityManagers for each EntityObject and put all the functionality on the manager side.  I soon got tired of instantiating an EntityManager every time I wanted to do a simple task like save or delete an object's record from the database, so I created "function pointers" that live in the state object, but point to the appropriate entity manager.  So I would have code in the state object that looked like this:

public int Save()
{
    return new Managers.AppointmentManager().Save(this);
}

public bool Delete()
{
    return new Managers.AppointmentManager().Delete(this);
}

This way, all the functionality still lives in the manager, but you can perform simple tasks from the state object.

We also wanted to be able to easily populate an object from the database.  Our Appointments table only stores a little information about the Client (first name, last name and ID), so if we populated an Appointment object from the database, we would have Appointment.Client, but Client would be underpopulated and calling Appointment.Client.HomeLocation would return empty information.  To get the rest of the information, we would have to instantiate a ClientManager, populate a Client object and assign it to Appointment.Client. 

So, I added a .Populate to my IEntity interface that all the state objects implement.  In the implementation, I would do the dirty work of creating a ClientManager, populating an object and assigning the properties of the new object to the current object.  That way, we could call Appointment.Client.Populate() and all the information about that client would live in the object (provided that ID or other identifying information existed in Appointment.Client).

As I was implementing the .Populate into our state objects, I realized that I was going to spend a lot of time going through each property and assigning it to this.  So I started playing around with System.Reflection and realized that I could create a single function in my base class that would accept an object as a parameter, iterate through it's properties and try to assign it to this.  So I added a function that looks like this:

internal bool Build(object obj)
{
    bool rv = false;
    try
    {
        if (obj == null)
        {
            throw new ApplicationException("obj is null");
        }
        //iterate through each property of this
        foreach (System.Reflection.PropertyInfo prop in this.GetType().GetProperties())
        {
            //get the name of the current property
            string memberName = prop.Name;
            //make sure this property is writeable and the obj property is readable
            if (prop.CanWrite && obj.GetType().GetProperty(memberName).CanRead)
            {
                //Get the value of this current property from obj
                object objValue = obj.GetType().GetProperty(memberName).GetValue(obj, null);
                //Set the value of this property from obj
                prop.SetValue(this, objValue, null);
            }
        }
        //if we are here, everything worked
        rv = true;
    }
    catch (Exception)
    {
        //something bad happened, return false
        rv = false;
    }
    return rv;
}

So now, when somebody calls Appointment.Client.Populate() the code to do the work is very simple:

 

public bool Populate()
{
    return base.Build((Client)new Managers.ClientManager().GetByID(this._id));
}

So the code creates a new ClientManager and calls GetByID, which hits the data access layer and returns an IEntity (the interface that all the state objects implement).  I cast the IEntity to a Client and send it to the base class to transfer the properties.

Obviously, I will need to add some code to ensure that this._id is populated and catch exceptions to throw to the calling code, but if the calling code just needs to know if it ended up getting populated, one line of code will work just fine.

So far it is working out really well.  But, this is my first real crack at reflection, so I would appreciate any pointers.

 

Posted on Friday, May 4, 2007 10:31 AM .NET , Goldstar | Back to top


Comments on this post: Architecture thoughts and Reflection

# re: Architecture thoughts and Reflection
Requesting Gravatar...
You could have also taken the approach of having your manager classes be static. The only real reason for classes to be instance classes is if they hold data, for which your manager classes do not. So in that case each manager class, and each of their methods, could be declared as static, thus saving you from having to actually instantiate each one very time.
Left by Dave Donaldson on May 07, 2007 11:00 AM

# re: Architecture thoughts and Reflection
Requesting Gravatar...
Very true, and I might still implement static functionality in my managers. I still like the idea of having "function pointers", so consumers can say
Appointment.Client.Populate()
instead of
Appointment.Client = ClientManager.GetByID(Appointment.Client.ID).
It just seems prettier :) Also, the consumer doesn't have to know how the database is designed. Sometimes .GetByID isn't implemented and they would have to call .GetByName. And since we plan to changed the DB architecure in the next release, any code that calls .Populate() will not break because we will change the function pointer to call the correct method from the manager.
Left by Tim Hibbard on May 07, 2007 11:14 AM

# re: Architecture thoughts and Reflection
Requesting Gravatar...
Agreed, the "function pointer" approach is very likeable :-)
Left by Dave Donaldson on May 07, 2007 11:18 AM

Your comment:
 (will show your gravatar)


Copyright © Tim Hibbard | Powered by: GeeksWithBlogs.net