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.