Property Assignment
July 24, 2012 Leave a comment
I am a big fan of separation of concerns and layering when it comes to application architecture. Using a dedicated service layer means that all client/calling layers are insulated from all dependencies past the service layer. In a usual n-tier architecture, that means that the layer(s) calling the service layer have no idea about the data tier behind the service.
Because of that, you can not use the concrete classes from the permanent data store as the data structures that carry data among the layers. Enter in the EF POCO generator! Using this add-in, you get EF integrated POCOs that are already WCF-ifed.
The downside is the update (or so I thought on Friday) – because you are using a different instance between service calls, you have to rehydrate a new object, assign in the new values, and then persist. This is very much the problem of EF classes when EF came out.
So I created a test project to see if I could use System.Reflection to help me do that assignment. I created a project with the following two classes:
public class Person { public int PersonId { get; set; } public String PersonName { get; set; } }
public class Student: Person { public String SchoolName { get; set; } }
Following TDD, I created a Test Project with the following test:
[TestMethod()] public void CreateStudentFromPerson_PersonNameAssigns_Test() { Person person = new Person(); person.PersonId = 0; person.PersonName = "Test"; StudentFactory studentFactory = new StudentFactory(); Student student = studentFactory.CreateStudentFromPerson(person); string expected = person.PersonName; string actual = student.PersonName; Assert.AreEqual(actual, expected); }
I then implemented the factory:
public Student CreateStudentFromPerson(Person person) { Student student = new Student(); PropertyInfo[] personPropertyInfos = typeof(Person).GetProperties(); PropertyInfo[] studentPropertyInfos = typeof(Student).GetProperties(); PropertyInfo studentPropertyInfo = null; foreach (PropertyInfo personPropertyInfo in personPropertyInfos) { studentPropertyInfo = studentPropertyInfos.FirstOrDefault(spi => spi.Name == personPropertyInfo.Name); if (studentPropertyInfo != null) { object personValue = personPropertyInfo.GetValue(person, null); studentPropertyInfo.SetValue(student, personValue, null); } } return student; }
Sure enough, the test ran green.
I then wanted to implement a generic method that would assign property values over regardless of the types in the arguments:
public Student CreateStudentFromPerson(Person person) { Student student = new Student(); return (Student)AssignObjectPropertyValues(person, student); } public Object AssignObjectPropertyValues(Object baseObject, Object targetObject) { PropertyInfo[] basePropertyInfos = baseObject.GetType().GetProperties(); PropertyInfo[] targetPropertyInfos = targetObject.GetType().GetProperties(); ; PropertyInfo targetPropertyInfo = null; foreach (PropertyInfo basePropertyInfo in basePropertyInfos) { targetPropertyInfo = targetPropertyInfos.FirstOrDefault(spi => spi.Name == basePropertyInfo.Name); if (targetPropertyInfo != null) { object basePropertyValue = basePropertyInfo.GetValue(baseObject, null); targetPropertyInfo.SetValue(targetObject, basePropertyValue, null); } } return targetObject; }
And wouldn’t you know: Green to go <trademark pending>!
MSDN warns about the performance cost, but I have to believe that my compiler is so good that there is negligible difference.
So then there was one more requirement, all Strings needed to be encrypted. I created another test like so:
[TestMethod()] public void CreateStudentFromPerson_PersonNameAssignsEncrypted_Test() { Person person = new Person(); person.PersonId = 0; person.PersonName = "Test"; StudentFactory studentFactory = new StudentFactory(); Student student = studentFactory.CreateStudentFromPerson(person,true); string expected = "XXXXX"; string actual = student.PersonName; Assert.AreEqual(actual, expected); }
with the “XXXXX” serving as a proxy for the actual encryption.
I then overloaded the method in the factory like so:
public Object AssignObjectPropertyValues(Object baseObject, Object targetObject, Boolean useEncryption) { PropertyInfo[] basePropertyInfos = baseObject.GetType().GetProperties(); PropertyInfo[] targetPropertyInfos = targetObject.GetType().GetProperties(); ; PropertyInfo targetPropertyInfo = null; foreach (PropertyInfo basePropertyInfo in basePropertyInfos) { targetPropertyInfo = targetPropertyInfos.FirstOrDefault(spi => spi.Name == basePropertyInfo.Name); if (targetPropertyInfo != null) { object basePropertyValue = basePropertyInfo.GetValue(baseObject, null); if (targetPropertyInfo.GetType() == typeof(String) && useEncryption) { targetPropertyInfo.SetValue(targetObject, "XXXXX", null); } else { targetPropertyInfo.SetValue(targetObject, basePropertyValue, null); } } } return targetObject; }
and had the original method chain:
public Student CreateStudentFromPerson(Person person, Boolean useEncryption) { Student student = new Student(); return (Student)AssignObjectPropertyValues(person, student, useEncryption); }
My test ran red:
Going back to the factory, I change the if condition to this:
if (targetPropertyInfo.PropertyType.Name == "String" && useEncryption)
And I was green to go <trademark pending>.
I then realized that Bob Martin would yell at me because I have boolean arguments. I then created an interface called IEncryptable like so:
public interface IEncryptable { Boolean IsEncrypted { get; set; } }
I then adjusted my method signatures to remove the boolean argument.
public Student CreateStudentFromPerson(Person person) { Student student = new Student(); return (Student)AssignObjectPropertyValues(person, student); }
and adjusted the AssignObjectPropertyValues to check if the targettype implements IEncryptable:
object basePropertyValue = basePropertyInfo.GetValue(baseObject, null); Type targetObjectType = targetObject.GetType(); if(targetObjectType.GetInterfaces().Contains(typeof(IEncryptable))) { if (targetPropertyInfo.PropertyType.Name == "String") { targetPropertyInfo.SetValue(targetObject, "XXXXX", null); } } else { targetPropertyInfo.SetValue(targetObject, basePropertyValue, null); }
Sure enough, the Encrypt Unit test passed but the unencrypt did not
Time to refactor! I added in a check for IEncryptable like so
if(targetObjectType.GetInterfaces().Contains(typeof(IEncryptable))) { PropertyInfo encryptedPropertyInfo = targetPropertyInfos.FirstOrDefault(tpi => tpi.Name == "IsEncrypted"); Boolean isEncrypted = (Boolean)encryptedPropertyInfo.GetValue(targetObject,null); if (isEncrypted) { if (targetPropertyInfo.PropertyType.Name == "String") { targetPropertyInfo.SetValue(targetObject, "XXXXX", null); } else { targetPropertyInfo.SetValue(targetObject, basePropertyValue, null); } } else { targetPropertyInfo.SetValue(targetObject, basePropertyValue, null); } } else { targetPropertyInfo.SetValue(targetObject, basePropertyValue, null); }
and I was green to go. I then noticed all of the nested if..thens and realized I should make some additional classes – EncryptedStudent perhaps. That would cut down on the cyclomic complexity.
After all that, I was informed of the ApplyCurrentValues method. This makes the application part of my solution useless – but not the encryption…