Встроенное (переопределить) отображение для вложенных типов с помощью AutoMapper - PullRequest
0 голосов
/ 23 мая 2019

Я хотел бы использовать AutoMapper и предоставить некоторые значения во время выполнения.

Например, у меня есть DTO & ViewModel.

Одно из свойств не существует в DTO и не может быть отображенонепосредственно с помощью Конвертеров / Резолверов / Трансформаторов;

namespace Lab.So.Sample
{
    public class UserDto
    {
        public int UserId { get; set; }
        public string UserCode { get; set; }
    }

    public class UserGroupDto
    {
        public List<UserDto> Users { get; set; }
    }

    public class UserViewModel
    {
        public int UserId { get; set; }
        public string FullName { get; set; }
    }

    public class UserGroup
    {
        public List<UserViewModel> Users { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var src = new Fixture().Create<UserGroupDto>();

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMissingTypeMaps = true;
                cfg.ValidateInlineMaps = false;
            });

            // How to hook mapping of nested object User->UserViewModel to provide value of FullName in runtime
            var dst = Mapper.Map<UserGroupDto, UserGroup>(src);
        }
    }
}

У меня есть какой-то обходной путь, как это сделать, но это не удобно для человека, как для меня:

class Program
    {
        internal class UserViewModelFullNameResolver : IValueResolver<UserDto, UserViewModel, string>
        {
            public string Resolve(UserDto source, UserViewModel destination, string destMember, ResolutionContext context)
            {
                var names = context.Items["ctx.Names"] as IDictionary<int, string>;

                if (names == null || !names.TryGetValue(source.UserId, out var fullName))
                {
                    return null;
                }

                return fullName;
            }
        }

        static void Main(string[] args)
        {
            var src = new Fixture().Create<UserGroupDto>();

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMissingTypeMaps = true;
                cfg.ValidateInlineMaps = false;

                cfg.CreateMap<UserDto, UserViewModel>()
                    .ForMember(d => d.FullName, opt => opt.MapFrom<UserViewModelFullNameResolver>());
            });

            var names = new Dictionary<int, string>
            {
                { 10, "FullName-10" },
                { 20, "FullName-20" },
            };

            var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Items["ctx.Names"] = names);
        }
    }

В этом обходном пути большинствонеудобно согласование имен ключей в opt.Items;Если что-то случайно сделало опечатку, это трудно исследовать и исправить;

Я выглядел примерно так:

var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Use(new UserViewModelFullNameResolver());

Другими словами, это определение экземпляра распознавателя во время выполнения для каждого уникального случая;

Также я бы согласился, если бы у меня была возможность определить крюк для отображения определенного типа в графе объектов:

var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt=>opt.Hook<UserDto,UserViewModel>((s,d)=> { /* any logic to read external data */ });

Пример использования:

var srcA = readDataA();
var srcB = readDataB();

var dst = Mapper.Map<UserGroupDto, UserGroup>(
  src, 
  opt=>opt.Hook<UserDto,UserViewModel>(
  (s,d)=> 
  {
      d.FullName = srcA + srcB; 
  });

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

1 Ответ

0 голосов
/ 23 мая 2019

Я смог использовать существующий API для архивирования своей цели:

public static class InlineMappingExtensions
    {
        public static void EnableInlineMapping(this IMapperConfigurationExpression cfg)
        {
            cfg.ForAllMaps((t, i) =>
            {
                i.AfterMap((s, d, ctx) =>
                    {
                        ctx.ApplyInlineMap(s, d);
                    }
                );
            });
        }

        public static IMappingOperationOptions OnMap<TSrc, TDst>(this IMappingOperationOptions opts,
            Action<TSrc, TDst> resolve)
        {
            var srcTypeName = typeof(TSrc);
            var dstTypeName = typeof(TDst);

            var ctxKey = $"OnMap_{srcTypeName}_{dstTypeName}";

            opts.Items.Add(ctxKey, resolve);

            return opts;
        }

        private static void ApplyInlineMap(this ResolutionContext opts, object src, object dst)
        {
            if (src == null)
            {
                return;
            }

            if (dst == null)
            {
                return;
            }

            var srcTypeName = src.GetType();
            var dstTypeName = dst.GetType();

            var ctxKey = $"OnMap_{srcTypeName}_{dstTypeName}";

            if (!opts.Items.TryGetValue(ctxKey, out var inlineMap))
            {
                return;
            }

            var act = inlineMap as Delegate;
            act?.DynamicInvoke(src, dst);
        }
    }

Чтобы включить его:

            Mapper.Initialize(cfg =>
            {
                cfg.CreateMissingTypeMaps = true;
                cfg.ValidateInlineMaps = false;


                cfg.EnableInlineMapping();
            });

Пример использования:

// read data from external sources
            var names = new Dictionary<int, string>
            {
                { 10, "FullName-10" },
                { 20, "FullName-20" },
            };

            Action<UserDto, UserViewModel> mapA = (s, d) =>
            {
                if (names.TryGetValue(s.UserId, out var name))
                {
                    d.FullName = name;
                }
            };

            Action<UserGroupDto, UserGroup> mapB = (s, d) =>
            {
                if (DateTime.Now.Ticks > 0)
                {
                    d.Users = null;
                }
            };

            var dst = Mapper.Map<UserGroupDto, UserGroup>(src, opt => opt.OnMap(mapA).OnMap(mapB));
...