DTO для Entity Mapping Tool - PullRequest
       7

DTO для Entity Mapping Tool

8 голосов
/ 19 марта 2012

У меня есть класс сущности 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 ]

6 голосов
/ 19 марта 2012

Вы можете взглянуть на два наиболее часто используемых средства отображения объектов:

AutoMapper

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

Значение Injecter

ValueInjecter позволяет вам определять собственное соответствие на основе соглашений алгоритмы (ValueInjection) для сопоставления (ввода) источника значения до значения назначения.

Есть статья для сравнения по SO: AutoMapper vs ValueInjecter

1 голос
/ 06 февраля 2013

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

http://inspire -software.com / слияния / дисплей / Геда / FAQ

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

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

Подробности здесь: http://inspire -software.com / confluence / display / GeDA / GeDA + - + Универсальный + DTO + Ассемблер

...