DTO到实体的映射工具 [英] DTO to Entity Mapping Tool

查看:65
本文介绍了DTO到实体的映射工具的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个实体类Person及其对应的DTO类PersonDto.

I have an entity class Person and its corresponding DTO class 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对象后,我必须将其转换为个人实体.现在,我完全手动完成此操作.代码看起来像这样.

After receiving a DTO object I have to convert it into a person entity. 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;
  }
}

该代码实际上是微不足道的.但是随着实体数量的增加,映射器类的数量也会增加.结果是很多类似的代码.另一个问题是,当存在模式关联时,我必须为其他存储库添加构造函数参数.我尝试注入某种存储库工厂,但是它闻到了一个不知名的Service Locator,所以我恢复了原始的解决方案.

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.

这些映射器的单元测试也导致了许多外观相似的测试方法.

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.

先谢谢了.

我已经用Value Injecter完成了任务,但是后来我意识到我可以安全地删除它,其余的仍然可以工作.这是解决方案.

I'd accomplished the task with Value Injecter but then I realized that I could safely remove it and the rest would still work. 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");
        }
    }  

使用从BaseEntityMapper派生的具体类对每个实体进行映射. Person实体的一个看起来像这样.

Mapping of each entity is performed with a concrete class derived from BaseEntityMapper. The one for Person entities looks like this.

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

Explicitly calling MapAssociation protects against future properties renamings.

推荐答案

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

You can have a look on the two most used Object-Object mapper:

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. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?

值注入器

ValueInjecter可让您定义自己的基于约定的匹配 算法(ValueInjections)以匹配(注入)源 值到目标值.

ValueInjecter lets you define your own convention-based matching algorithms (ValueInjections) in order to match up (inject) source values to destination values.

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

There is a comparison article on SO: AutoMapper vs ValueInjecter

这篇关于DTO到实体的映射工具的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆