So how to handle contact info, do I make a "type level" domain object like an Emergency or Home, or do I save that sort of logical grouping for the Business layer. Truthfully I've already made up my mind. The domain layer I'm updating doesn't make any groupings, and here's why. Essentially I tried to look at the domain objects as whole entities onto themselves. An email address really has nothing to do with a mailing address, and any association like "emergency" is purely a matter of interpretation. Another way to look at it is I didn't really want my domain layer making what I thought was a business decision to allow something like an emergency email. It's the prerogative of the BLL to decide what Types of each entity the system can created. Perhaps for the grouping of emergency the BLL determines that Phone-Number is the only valid entity to create and maintain.
So if that sounds like a reasonable approach then it's not too hard to draw out where I'm going next. One change I've made though is to switch to Nullable<int>s for my IDs. I originally created this domain set in .Net 1.1 so that really wasn't a possibility. But now that I can, it can become a nice way to track new domain objects that must be persisted. This brings up a whole entire ball of scariness I haven't yet covered, how to track the dirty state of an Domain object, let me count the ways. First and foremost let me say that this is the sole potential benefit that I see in using DataSets instead of Domain objects, but it's not worth it by any means.
Handling the notion of a Dirty object, is a touchy one topic, both because it can provide a fair number of challenges, and because there are so many preferred patterns to use. However generally I tend to cheat here especially when it comes to making any decision about whether to persist/update/delete a domain object I rely solely on the BLL to make the call. Because I generally try to follow a pattern where any UI, or API that can create or modify data has absolutely no contact with domain objects, I can push any state logic out of the domain layer allowing for a very simple model. I can't say if this is the best model, only that I like idea that a least one application layer can be very simple.
So without further ado here's the ContactInfo Class and its constituents:
public class ContactInfo
{
public int? ContactInfoID { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
public List<Address> Addresses { get; set; }
public List<EmailAddress> EmailAddresses { get; set; }
public ContactInfo()
{
}
public ContactInfo(int? contactInfoID, List<PhoneNumber> phoneNumbers, List<Address> addresses, List<EmailAddress> emailAddresses)
{
this.ContactInfoID = contactInfoID;
this.PhoneNumbers = phoneNumbers;
this.Addresses = addresses;
this.EmailAddresses = emailAddresses;
}
}
public class EmailAddress
{
public int? AddressID { get; set; }
public string Address { get; set; }
public Status Status { get; set; }
public int Order { get; set; }
public EmailAddress()
{
}
public EmailAddress(int? addressID, string address, Status status, int order)
{
this.AddressID = addressID;
this.Address = address;
this.Status = status;
this.Order = order;
}
}
public class PhoneNumber
{
public int? PhoneNumberID { get; set; }
public string Number { get; set; }
public PhoneNumberType PhoneNumberType { get; set; }
public PhoneNumber()
{
}
public PhoneNumber(int? phoneNumberID, string number, PhoneNumberType phoneNumberType)
{
this.PhoneNumberID = phoneNumberID;
this.Number = number;
this.PhoneNumberType = phoneNumberType;
}
}
public class Address
{
public int? AddressID { get; set; }
public AddressType AddressType { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public Address()
{
}
public Address(int? addressID,
AddressType addressType,
string street,
string city,
string state,
string zip)
{
this.AddressID = addressID;
this.AddressType = addressType;
this.Street = street;
this.City = city;
this.State = state;
this.Zip = zip;
}
}
That's it not too much there, but next we'll look at the Roles side of the Person Tree.
Okay one more distraction, although I'm not yet focusing on unit tests, I'm going to include some code that I often use to support my unit tests. This class is used to create a default instance of a complex object. With my current code base I don't have a lot of use for it, but I'd like to throw it out there as I know it will be useful to me later, and hopefully someone will be able to turn it into something much more useful.
Object Creator
public class DefaultObjectCreator<T>
{
public DefaultObjectCreator()
{
if (_propertyValues == null)
{
_propertyValues = new Hashtable();
}
_objectType = typeof(T);
}
public DefaultObjectCreator<T> CreateObject()
{
Assembly asm = Assembly.GetAssembly(_objectType);
List<object> args = new List<object>();
ConstructorInfo ctor = selectConstructor(_objectType);
if (null == ctor)
{
throw new ApplicationException("Could not match provided parameters to any Constructor");
}
foreach (ParameterInfo pi in ctor.GetParameters())
{
if (_propertyValues.ContainsKey(pi.Name))
{
args.Add(_propertyValues[pi.Name]);
}
else
{
args.Add(getDefaultTypeValue(pi.ParameterType));
}
}
_createdObject = (T)asm.CreateInstance(_objectType.FullName, true, BindingFlags.CreateInstance, null, args.ToArray(), null, null);
return this;
}
public DefaultObjectCreator<T> SetValue(string paramName, object value)
{
if (null == _createdObject)
{
_propertyValues[paramName] = value;
return this;
}
foreach (PropertyInfo pi in _objectType.GetProperties())
{
if (pi.Name.ToLower() == paramName.ToLower())
{
pi.SetValue(_createdObject, value, null);
return this;
}
}
foreach (FieldInfo fi in _objectType.GetFields())
{
if (fi.Name.ToLower() == paramName.ToLower())
{
fi.SetValue(_createdObject, value);
return this;
}
}
throw new ApplicationException(string.Format("Could not find property [{0}]", paramName));
}
public T GetObject()
{
if (null == _createdObject)
{
CreateObject();
}
return _createdObject;
}
private ConstructorInfo selectConstructor(Type objectType)
{
ConstructorInfo[] ctors = objectType.GetConstructors();
if (_propertyValues.Count == 0)
{
foreach (ConstructorInfo ctor in ctors)
{
if (ctor.GetParameters().Length == 0)
{
return ctor;
}
}
return null;
}
foreach (ConstructorInfo ctor in ctors)
{
if (ctor.GetParameters().Length < _propertyValues.Count)
{
continue;
}
bool found = false;
foreach (string paramName in _propertyValues.Keys)
{
foreach (ParameterInfo pi in ctor.GetParameters())
{
if (pi.Name.ToLower() == paramName.ToLower())
{
found = true;
break;
}
}
if (!found)
{
continue;
}
}
if (found)
{
return ctor;
}
}
return null;
}
private object getDefaultTypeValue(Type paramType)
{
if (paramType == typeof(bool))
{
return false;
}
if (paramType == typeof(DateTime))
{
return DateTime.MinValue;
}
if (paramType.IsValueType)
{
if (paramType.IsEnum)
{
return Enum.GetValues(paramType).GetValue(0);
}
else if (paramType.IsPrimitive)
{
return 0;
}
return createDefaultObject(paramType);
}
return null;
}
private object createDefaultObject(Type objectType)
{
Assembly asm = Assembly.GetAssembly(objectType);
List<object> args = new List<object>();
ConstructorInfo[] ctors = objectType.GetConstructors();
foreach (ParameterInfo pi in ctors[0].GetParameters())
{
args.Add(getDefaultTypeValue(pi.ParameterType));
}
return asm.CreateInstance(objectType.FullName, true, BindingFlags.CreateInstance, null, args.ToArray(), null, null);
}
private Hashtable _propertyValues;
private T _createdObject;
private Type _objectType;
}
Print | posted on Friday, October 26, 2007 10:47 PM