Pull to refresh

Немного о нетрадиционном маппинге

Reading time 2 min
Views 8.2K
Многие знают про отличную библиотеку AutoMapper. С преобразованием Entity -> Dto проблем обычно не возникает. Но как обрабатывать обратный маппинг в случае, когда в API приходит корень агрегации? Хорошо, если read и write — контексты разделены и писать можно из Dto. Чаще, однако, нужно выбрать соответствующие сущности из ORM по Id и сохранить агрегат целиком. Занятие это муторное, однако зачастую поддающееся автоматизации.

Объявляем такой TypeConverter:

public class EntityTypeConverter<TDto, TEntity> : ITypeConverter<TDto, TEntity>
        where TEntity: PersistentObject, new()
    {
        public TEntity Convert(ResolutionContext context)
        {
            // Забираем из контейнера DbContext
            // Да, ServiceLocator это плохо, но о том как прикрутить IOC вы сможете и сами прочесть по ссылке
            // http://stackoverflow.com/questions/4204664/automapper-together-with-dependency-injection
            var dc = ApplicationContext.Current.Container.Resolve<IDbContext>();
            var sourceId = (context.SourceValue as IEntity)?.Id;

            var dest = context.DestinationValue as TEntity
                        ?? (sourceId.HasValue && sourceId.Value != 0 ? dc.Get<TEntity>(sourceId.Value) : new TEntity());

            // Да, reflection, да медленно и может привести к ошибкам в рантайме.
            // Можете написать Expression Trees, скомпилировать и закешировать для производительности
            // И анализатор для проверки корректности Dto на этапе компиляции
            var sp = typeof(TDto)
                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(x => x.CanRead && x.CanWrite)
                .ToDictionary(x => x.Name.ToUpper(), x => x);

            var dp = typeof(TEntity)
                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(x => x.CanRead && x.CanWrite)
                .ToArray();

            // проходимся по всем свойствам целевого объекта
            foreach (var propertyInfo in dp)
            {
                var key = propertyInfo.PropertyType.InheritsOrImplements(typeof(PersistentObject))
                    ? propertyInfo.Name.ToUpper() + "ID"
                    : propertyInfo.Name.ToUpper();

                if (sp.ContainsKey(key))
                {
                    // маппим один к одному примитивы, связанные сущности тащим из контекста
                    propertyInfo.SetValue(dest, key.EndsWith("ID")
                        && propertyInfo.PropertyType.InheritsOrImplements(typeof(PersistentObject))
                            ? dc.Get(propertyInfo.PropertyType, sp[key].GetValue(context.SourceValue))
                            : sp[key].GetValue(context.SourceValue));
                }
            }  

            return dest;
        }
    }

И создаем маппинг:

AutoMapper.Mapper
                .CreateMap<TDto, TEntity>()
                .ConvertUsing<EntityTypeConverter<TDto, TEntity>>();
Tags:
Hubs:
+9
Comments 9
Comments Comments 9

Articles