DTO для 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 я должен преобразовать его в личность. Сейчас я делаю это полностью вручную. Код выглядит так.

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 защищает от переименования будущих свойств.

2 ответа

Вы можете взглянуть на два наиболее используемых Object-Object Mapper:

AutoMapper

AutoMapper - это простая небольшая библиотека, созданная для решения обманчиво сложной проблемы - избавления от кода, который отображает один объект на другой. Этот тип кода довольно тоскливо и скучно писать, так почему бы не изобрести инструмент, который сделает это для нас?

Значение Injecter

Value Injecter позволяет вам определять свои собственные основанные на соглашении алгоритмы сопоставления (ValueInjection), чтобы сопоставить (ввести) исходные значения с целевыми значениями.

Есть статья сравнения на SO: AutoMapper против Value Injecter

Вы можете использовать GeDA для отображения любого объекта на объект DTO, он поставляется либо с аннотациями, либо с поддержкой DSL.

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

В вики есть только базовые примеры, но jUnits исходного кода полны полезных примеров

Вы можете получить его из sourceforge или кода Google вручную или через зависимости от maven.

Подробности здесь: http://inspire-software.com/confluence/display/GeDA/GeDA+-+Generic+DTO+Assembler

Другие вопросы по тегам