Playing around with incorporating Data Transfer Objects into my project, I figure I can use a touch of reflection to manage most assembler cases which are called from my service layer. May be a tad slower, but worth the savings for coding a million assembler classes. My base assembler has some basic methods for mapping simple properties to my entities suchas :
using System;
using System.Collections.Generic;
using System.Text;
using Cei.eMerge.Core.Domain;
using Cei.eMerge.Core.DaoInterfaces;
using Cei.eMerge.DTO;
using Cei.eMerge.Data;
using Cei.eMerge.Data.NHibernate;
using System.Reflection;
namespace Cei.eMerge.DomainFacade.Assemblers
{
internal abstract class DTOAssemblerBase
{
public DTOAssemblerBase() { }
private IDaoFactory _daoFactory;
///
/// Wires up factory to NHibernate Dao strategy for hydration of objects. This can be
/// changed later to be a different provider.
///
public IDaoFactory DaoFactory
{
get
{
if (_daoFactory == null)
{
_daoFactory = new NHibernateDaoFactory();
}
return _daoFactory;
}
}
protected virtual void MapEntityToDto(TEntity entity,DTOBase dto) where TEntity:DomainEntityBase
{
PropertyInfo[] props = dto.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo info in props)
{
PropertyInfo entityProp = entity.GetType().GetProperty(info.Name);
if (entityProp != null)
{
info.SetValue(dto, entityProp.GetValue(entity, null),null);
}
}
}
protected virtual void MapDtoToEntity(TEntity entity, DTOBase dto) where TEntity : DomainEntityBase
{
PropertyInfo[] props = entity.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo info in props)
{
if (!info.Name.Equals("Id"))
{
PropertyInfo dtoProp = dto.GetType().GetProperty(info.Name);
if (dtoProp != null)
{
info.SetValue(entity, dtoProp.GetValue(dto, null), null);
}
}
}
}
protected virtual void Validate()
{
}
}
}
And then my GenericAssembler to call from my service object is:
using System;
using System.Collections.Generic;
using System.Text;
using Cei.eMerge.Core.Domain;
using Cei.eMerge.DTO;
using Cei.eMerge.Core.DaoInterfaces;
using Cei.eMerge.Data;
using Cei.NHibernateCore;
using System.Reflection;
namespace Cei.eMerge.DomainFacade.Assemblers
{
///
/// Assembler for generic fetching of entities and their corresponding DTO's.
/// Read-only access to entities is only permitted here, so any SaveOrUpdate commands
/// must be executed outside this object.
///
///
/// This operates assuming the following conventions:
///
/// DTO property names are exactly (case-sensitive) matching the Domain Entity property names.
/// The target entity has a corresponding DAO method registered in teh IDaoFactory as
/// GetclassNameDao
///
///
///
///
internal class GenericAssembler<TEntity,TDto> : DTOAssemblerBase where TEntity:DomainEntityBase,new()
where TDto:DTOBase,new()
{
internal GenericAssembler() { }
public TDto GetDTO(TEntity entity)
{
TDto dto = new TDto();
MapEntityToDto(entity, dto);
return dto;
}
///
/// Mock service locator using reflection. Since naming conventions are assumed (GetEntityDao()) we
/// can safely avoid a formal configuration framework for this...AH RUBY!
///
///
private MethodInfo GetDaoMethod()
{
string typeName = typeof(TEntity).UnderlyingSystemType.Name;
string getMethod = "Get" + typeName + "Dao";
MethodInfo method = DaoFactory.GetType().GetMethod(getMethod);
if(method!=null)
{
return method;
}
string MISSING_DAO =
"A Dao corresponding to {0} (a method named '{1}') could not be found.";
throw InvalidOperationException(string.Format(MISSING_DAO,typeName,getMethod));
}
public TEntity CreateEntity(TDto dto)
{
MethodInfo method = GetDaoMethod();
IDaoBase<TEntity, int> dao = (IDaoBase<TEntity, int>)method.Invoke(DaoFactory, null);
TEntity entity = new TEntity();
MapDtoToEntity(entity, dto);
return entity;
}
public TEntity UpdateEntity(int id, TDto dto)
{
MethodInfo method = GetDaoMethod();
IDaoBase<TEntity, int> dao = (IDaoBase<TEntity, int>)method.Invoke(DaoFactory, null);
TEntity entity = dao.GetById(id, true);
MapDtoToEntity(entity, dto);
return entity;
}
}
}
