DTO to Entity Mapping Tool
Solution 1
You can have a look on the two most used Object-Object mapper:
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 lets you define your own convention-based matching algorithms (ValueInjections) in order to match up (inject) source values to destination values.
There is a comparison article on SO: AutoMapper vs ValueInjecter
Solution 2
You can use GeDA for mapping any entity to a DTO object, it comes with either annotations or DSL support.
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
You can get it from sourceforge or google code manually or via maven dependency
Details are here: http://inspire-software.com/confluence/display/GeDA/GeDA+-+Generic+DTO+Assembler
Comments
-
Dmitriy Melnik almost 2 years
I have an entity class
Person
and its corresponding DTO classPersonDto
.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. 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.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
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"); } }
Mapping of each entity is performed with a concrete class derived from
BaseEntityMapper
. The one forPerson
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); } }
Explicitly calling
MapAssociation
protects against future properties renamings. -
Dmitriy Melnik about 12 yearsThanks.
Value Injecter
have guided me to the final solution that I described in my post update. -
RayLoveless over 8 yearsCareful using these libraries( automapper at least ) because they use reflection to do the mappings and kill your performance! I recently saw some code that was mapping a collection of 100 objects and it was taking 3 seconds. I wouldn't use these libraries. You may be care but developers that come after will abuse them.