简体   繁体   English

DTO到实体映射工具

[英]DTO to Entity Mapping Tool

I have an entity class Person and its corresponding DTO class PersonDto . 我有一个实体类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; }
}

After receiving a DTO object I have to convert it into a person entity. 收到DTO对象后,我必须将其转换为person实体。 Now I do it completely manually. 现在我完全手动完成。 The code looks like this. 代码看起来像这样。

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;
  }
}

The code is in fact trivial. 代码实际上是微不足道的。 But as the number of entities grows so does the number of mapper classes. 但随着实体数量的增加,映射器类的数量也会增加。 The result is lots of similar code. 结果是许多类似的代码。 Another issue is that when there are mode associations I have to add constructor parameteres for additional repositories. 另一个问题是,当存在模式关联时,我必须为其他存储库添加构造函数参数。 I tried to inject a some kind of a repository factory instead, but it smelled a bad-known Service Locator so I reverted to an original solution. 我尝试注入某种类型的存储库工厂,但它闻到了一个鲜为人知的Service Locator所以我恢复了原来的解决方案。

Unit testing of these mappers also results in a number of similar-looking test methods. 这些映射器的单元测试还产生了许多类似的测试方法。

With all this been said I wonder if there exists a solution that can reduce the amount of manually written code and make the unit testing easier. 有了这一切,我想知道是否存在可以减少手动编写代码量并使单元测试更容易的解决方案。

Thanks in advance. 提前致谢。

UPDATE UPDATE

I'd accomplished the task with Value Injecter but then I realized that I could safely remove it and the rest would still work. 我用Value Injecter完成了任务但后来我意识到我可以安全地删除它,其余的仍然可以工作。 Here is the resulting solution. 这是最终的解决方案。

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");
        }
    }  

Mapping of each entity is performed with a concrete class derived from BaseEntityMapper . 使用从BaseEntityMapper派生的具体类来执行每个实体的映射。 The one for Person entities looks like this. 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);
        }
    }

Explicitly calling MapAssociation protects against future properties renamings. 明确调用MapAssociation可以防止将来的属性重命名。

You can have a look on the two most used Object-Object mapper: 您可以查看最常用的两个Object-Object映射器:

AutoMapper AutoMapper

AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. AutoMapper是一个简单的小型库,用于解决一个看似复杂的问题 - 摆脱将一个对象映射到另一个对象的代码。 This type of code is rather dreary and boring to write, so why not invent a tool to do it for us? 这种类型的代码是相当沉闷和无聊的写,所以为什么不发明一个工具来为我们做?

Value Injecter 价值注入

ValueInjecter lets you define your own convention-based matching algorithms (ValueInjections) in order to match up (inject) source values to destination values. ValueInjecter允许您定义自己的基于约定的匹配算法(ValueInjections),以便将源值匹配(注入)到目标值。

There is a comparison article on SO: AutoMapper vs ValueInjecter 关于SO的比较文章: AutoMapper与ValueInjecter

You can use GeDA for mapping any entity to a DTO object, it comes with either annotations or DSL support. 您可以使用GeDA将任何实体映射到DTO对象,它带有注释或DSL支持。

http://inspire-software.com/confluence/display/GeDA/FAQ http://inspire-software.com/confluence/display/GeDA/FAQ

There are only basic examples on the wiki but jUnits of source code are full of useful examples 维基上只有基本的例子,但源代码的jUnits充满了有用的例子

You can get it from sourceforge or google code manually or via maven dependency 您可以手动或通过maven依赖从sourceforge或Google代码获取它

Details are here: http://inspire-software.com/confluence/display/GeDA/GeDA+-+Generic+DTO+Assembler 详细信息如下: 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