繁体   English   中英

DTO到实体映射工具

[英]DTO to Entity Mapping Tool

我有一个实体类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所以我恢复了原来的解决方案。

这些映射器的单元测试还产生了许多类似的测试方法。

有了这一切,我想知道是否存在可以减少手动编写代码量并使单元测试更容易的解决方案。

提前致谢。

UPDATE

我用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可以防止将来的属性重命名。

您可以查看最常用的两个Object-Object映射器:

AutoMapper

AutoMapper是一个简单的小型库,用于解决一个看似复杂的问题 - 摆脱将一个对象映射到另一个对象的代码。 这种类型的代码是相当沉闷和无聊的写,所以为什么不发明一个工具来为我们做?

价值注入

ValueInjecter允许您定义自己的基于约定的匹配算法(ValueInjections),以便将源值匹配(注入)到目标值。

关于SO的比较文章: AutoMapper与ValueInjecter

您可以使用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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM