我有一个实体类Person
及其对应的DTO类PersonDto
。
public class Person: Entity
{
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual Sex Sex { get; set; }
public virtual Position Position { get; set; }
public virtual Division Division { get; set; }
public virtual Organization Organization { get; set; }
}
public class PersonDto: Dto
{
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public Guid SexId { get; set; }
public Guid PositionId { get; set; }
public Guid DivisionId { get; set; }
public Guid OrganizationId { get; set; }
}
收到DTO对象后,我必须将其转换为person实体。现在我完全手动完成。代码看起来像这样。
public class PersonEntityMapper: IEntityMapper<Person, PersonDto>
{
private IRepository<Person> _personRepository;
private IRepository<Sex> _sexRepository;
private IRepository<Position> _positionRepository;
private IRepository<Division> _divisionRepository;
private IRepository<Organization> _organizationRepository;
public PersonEntityMapper(IRepository<Person> personRepository,
IRepository<Sex> sexRepository,
IRepository<Position> positionRepository,
IRepository<Division> divisionRepository,
IRepository<Organization> organizationRepository)
{
... // Assigning repositories
}
Person Map(PersonDto dto)
{
Person person = CreateOrLoadPerson(dto);
person.Name = dto.Name;
person.Phone = dto.Phone;
person.Email = dto.Email;
person.Sex = _sexRepository.LoadById(dto.SexId);
person.Position = _positionRepository.LoadById(dto.PositionId);
person.Division = _divisionRepository.LoadById(dto.DivisionId);
person.Organization = _organizationRepository.LoadById(dto.OrganizationId);
return person;
}
}
代码实际上是微不足道的。但随着实体数量的增加,映射器类的数量也会增加。结果是许多类似的代码。另一个问题是,当存在模式关联时,我必须为其他存储库添加构造函数参数。我尝试注入某种类型的存储库工厂,但它闻到了一个鲜为人知的Service Locator
,所以我恢复了原来的解决方案。
这些映射器的单元测试也会产生许多类似的测试方法。
有了这一切,我想知道是否存在可以减少手动编写代码量并使单元测试更容易的解决方案。
提前致谢。
我用Value Injecter
完成了任务,但后来我意识到我可以安全地删除它,其余的仍然有用。这是最终的解决方案。
public abstract class BaseEntityMapper<TEntity, TDto> : IEntityMapper<TEntity, TDto>
where TEntity : Entity, new()
where TDto : BaseDto
{
private readonly IRepositoryFactory _repositoryFactory;
protected BaseEntityMapper(IRepositoryFactory repositoryFactory)
{
_repositoryFactory = repositoryFactory;
}
public TEntity Map(TDto dto)
{
TEntity entity = CreateOrLoadEntity(dto.State, dto.Id);
MapPrimitiveProperties(entity, dto);
MapNonPrimitiveProperties(entity, dto);
return entity;
}
protected abstract void MapNonPrimitiveProperties(TEntity entity, TDto dto);
protected void MapPrimitiveProperties<TTarget, TSource>(TTarget target, TSource source, string prefix = "")
{
var targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
var sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
foreach (var targetProperty in targetProperties) {
foreach (var sourceProperty in sourceProperties) {
if (sourceProperty.Name != string.Format("{0}{1}", prefix, targetProperty.Name)) continue;
targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
break;
}
}
}
protected void MapAssociation<TTarget, T>(TTarget target, Expression<Func<T>> expression, Guid id) where T : Entity
{
var repository = _repositoryFactory.Create<T>();
var propertyInfo = (PropertyInfo)((MemberExpression)expression.Body).Member;
propertyInfo.SetValue(target, repository.LoadById(id), null);
}
private TEntity CreateOrLoadEntity(DtoState dtoState, Guid entityId)
{
if (dtoState == DtoState.Created) return new TEntity();
if (dtoState == DtoState.Updated) {
return _repositoryFactory.Create<TEntity>().LoadById(entityId);
}
throw new BusinessException("Unknown DTO state");
}
}
使用从BaseEntityMapper
派生的具体类来执行每个实体的映射。 Person
实体的实体看起来像这样。
public class PersonEntityMapper: BaseEntityMapper<Person, PersonDto>
{
public PersonEntityMapper(IRepositoryFactory repositoryFactory) : base(repositoryFactory) {}
protected override void MapNonPrimitiveProperties(Person entity, PersonDto dto)
{
MapAssociation(entity, () => entity.Sex, dto.SexId);
MapAssociation(entity, () => entity.Position, dto.PositionId);
MapAssociation(entity, () => entity.Organization, dto.OrganizationId);
MapAssociation(entity, () => entity.Division, dto.DivisionId);
}
}
明确调用MapAssociation
可以防止将来的属性重命名。
答案 0 :(得分:6)
您可以查看两个最常用的Object-Object映射器:
AutoMapper是一个简单的小型库,用于欺骗性地解决问题 复杂的问题 - 摆脱映射一个对象的代码 另一个。这种类型的代码相当沉闷,写起来很无聊,所以 为什么不为我们发明一个工具?
ValueInjecter允许您定义自己的基于约定的匹配 算法(ValueInjections)以匹配(注入)源 值到目标值。
有一篇关于SO的比较文章:AutoMapper vs ValueInjecter
答案 1 :(得分:1)
您可以使用GeDA将任何实体映射到DTO对象,它带有注释或DSL支持。
http://inspire-software.com/confluence/display/GeDA/FAQ
维基上只有基本的例子,但源代码的jUnits充满了有用的例子
您可以手动或通过maven依赖从sourceforge或Google代码获取
详细信息如下:http://inspire-software.com/confluence/display/GeDA/GeDA+-+Generic+DTO+Assembler