Automapper - получить все записи в резольвере - PullRequest
1 голос
/ 28 мая 2019

Я работаю над проектом, в значительной степени полагающимся на Automapper, и в большинстве случаев мы отображаем полные наборы данных в модели набора представлений, например

IEnumerable<ObjectA> ListOfObjectA = MockupOfObjectA;
IEnumerable<ViewModelA> = Mapper.Map<IEnumerable<ObjectA>>(ListOfOjectA)

В настройках сопоставления мы используем пользовательские распознаватели благодаря IMemberValueResolver. Параметры и доступные данные в Resolve и ResolveStatic-методе - это только текущая сопоставляемая сущность. Можно ли получить доступ к полному источнику (ListOfOjectA) в этом случае внутри распознавателя?

Пока что я добавляю ListOfOjectA в MappingOperationsOptions.Items и использую их из context.Items, но это обходной путь, с которым нелегко работать и который плохо масштабируется.

Надеюсь, я сделал свой вопрос относительно ясным.

Ответы [ 3 ]

1 голос
/ 10 июня 2019

Стоит отметить, что вы на самом деле не отображаете ObjectA в ViewModelA. Скорее (ObjectA, List<ObjectA>) до ViewModelA, поскольку вы не можете определить ViewModelA без List<ObjectA>.

Для моделирования, скажем, ObjectA имеет свойство Index, а также число Pages, которое оно содержит.

public class ObjectA
{
    public int Index { get; set; }
    public int Pages { get; set; }
    public string MyProperty { get; set; }
}

А для ViewModelA мы хотим разрешить StartPage, основываясь на свойствах предыдущих ObjectA.

public class ViewModelA
{
    public int StartPage { get; set; }
    public string MyProperty { get; set; }
}

Мы можем очистить ваш текущий подход, используя методы расширения.

public static class AutoMapperExt
{
    public static TDestination MapWithSource<TSource, TDestination>(this IMapper mapper, TSource source)
        => mapper.Map<TSource, TDestination>(source, opts => opts.Items[typeof(TSource).ToString()] = source);

    public static TSource GetSource<TSource>(this ResolutionContext context)
        => (TSource)context.Items[typeof(TSource).ToString()];
}

Используя эти методы, нам больше не нужно напрямую обрабатывать коллекцию Items контекста.

class Program
{
    static void Main(string[] args)
    {
        var config =
            new MapperConfiguration(cfg =>
                cfg.CreateMap<ObjectA, ViewModelA>()
                   .ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver, int>(src => src.Index))
            );
        var mapper = config.CreateMapper();

        var source = new List<ObjectA>
        {
            new ObjectA { Index = 0, Pages = 3, MyProperty = "Foo" },
            new ObjectA { Index = 1, Pages = 2, MyProperty = "Bar" },
            new ObjectA { Index = 2, Pages = 1, MyProperty = "Foz" },
        };

        var result = mapper.MapWithSource<List<ObjectA>, List<ViewModelA>>(source);

        result.ForEach(o => Console.WriteLine(o.StartPage)); // prints 1,4,6
        Console.ReadKey();
    }
}

public class CustomResolver : IMemberValueResolver<object, object, int, int>
{
    public int Resolve(object source, object destination, int sourceMember, int destMember, ResolutionContext context)
    {
        var index = sourceMember;
        var list = context.GetSource<List<ObjectA>>();

        var pages = 1;
        for (int i = 0; i < index; i++)
        {
            pages += list[i].Pages;
        }

        return pages;
    }
}

Если вы хотите повторно использовать CustomResolver в разных классах, вы можете абстрагировать свойства, с которыми он работает, в интерфейс.

public interface IHavePages
{
    int Index { get; }
    int Pages { get; }
}

public class ObjectA : IHavePages
{
    public int Index { get; set; }
    public int Pages { get; set; }
    public string MyProperty { get; set; }
}

Таким образом, распознаватель больше не привязан к конкретной реализации. Теперь мы можем даже использовать интерфейс в качестве параметра типа.

public class CustomResolver : IMemberValueResolver<IHavePages, object, int, int>
{
    public int Resolve(IHavePages source, object destination, int sourceMember, int destMember, ResolutionContext context)
    {
        var hasPages = source;
        var index = sourceMember;
        var list = context.GetSource<List<IHavePages>>();

        var pages = 1;
        for (int i = 0; i < index; i++)
        {
            pages += list[i].Pages;
        }

        return pages;
    }
}

Все, что нам нужно сделать, это преобразовать наш List<ObjectA> перед отображением.

var listOfObjectA = new List<ObjectA>
{
    new ObjectA { Index = 0, Pages = 3, MyProperty = "Foo" },
    new ObjectA { Index = 1, Pages = 2, MyProperty = "Bar" },
    new ObjectA { Index = 2, Pages = 1, MyProperty = "Foz" },
};
var source = listOfObjectA.OfType<IHavePages>().ToList();
var result = mapper.MapWithSource<List<IHavePages>, List<ViewModelA>>(source);

// AutoMapper still maps properties that aren't part of the interface
result.ForEach(o => Console.WriteLine($"{o.StartPage} - {o.MyProperty}"));

Как только вы кодируете интерфейс, sourceMember в CustomResolver становится избыточным. Теперь мы можем получить его через пройденный source. С учетом одного окончательного рефакторинга, как мы получаем из IValueResolver вместо IMemberValueResolver.

public class CustomResolver : IValueResolver<IHavePages, object, int>
{
    public int Resolve(IHavePages source, object destination, int destMember, ResolutionContext context)
    {
        var list = context.GetSource<List<IHavePages>>();

        var pages = 1;
        for (int i = 0; i < source.Index; i++)
        {
            pages += list[i].Pages;
        }

        return pages;
    }
}

Обновление подписи.

cfg.CreateMap<ObjectA, ViewModelA>()
   .ForMember(dest => dest.StartPage, opt => opt.MapFrom<CustomResolver>())

Как далеко вы берете это, зависит только от вас, но вы можете улучшить повторное использование кода, введя абстракции.

0 голосов
/ 11 июня 2019

Если вы предпочитаете не использовать ResolutionContext, вы можете настроить отображение через промежуточный объект, содержащий как текущий элемент источника, так и полный список источников.
Используйте легкий тип значения, например.Tuple или ValueTuple .

В приведенном ниже отображении используется ValueTuple (но также может быть выражено с помощью Tuple).
Обратите внимание, что намерение и предпосылкиэто отображение довольно явное;это означает, что требуются 2 элемента ввода / источника: ObjectA и IEnumerable<ObjectA> (переданные через ValueTuple) .

Mapper.Initialize(cfg =>
    cfg.CreateMap<(ObjectA, IEnumerable<ObjectA>), ViewModelA>()
       .ForMember(
           dest => dest.Name,
           opt => opt.MapFrom<CustomResolver>()
       ));            

Во время отображения вы проецируетесписок источников в один из соответствующих ValueTuple с.
Предпочитают поддерживать потоковую передачу, используя только 1 текущий ValueTuple.

var viewModels = 
    Mapper.Map<IEnumerable<ViewModelA>>(
        ListOfObjectA.Select(o => (o, ListOfObjectA))
        );

Пользовательский IValueResolver получаеткак текущий элемент ввода, так и полный список через аргумент источника ValueTuple.

public class CustomResolver :
    IValueResolver<
        (ObjectA Item, IEnumerable<ObjectA> List),
        ViewModelA,
        String
        >
{
    public string Resolve(
        (ObjectA Item, IEnumerable<ObjectA> List) source,
        ViewModelA destination, 
        string destMember, 
        ResolutionContext context
        )
    {
        /* Retrieve something via the list. */
        var suffix = source.List.Count().ToString(); 
        return $"{source.Item.Name} {suffix}";
    }
}

Полный пример.

IEnumerable<ObjectA> ListOfObjectA = new List<ObjectA> {
    new ObjectA { Name = "One" },
    new ObjectA { Name = "Two" },
    new ObjectA { Name = "Three" }
    };

Mapper.Initialize(cfg =>
    cfg.CreateMap<(ObjectA, IEnumerable<ObjectA>), ViewModelA>()
        .ForMember(
            dest => dest.Name,
            opt => opt.MapFrom<CustomResolver>()
        ));                

var viewModels = 
    Mapper.Map<IEnumerable<ViewModelA>>(
        ListOfObjectA.Select(o => (o, ListOfObjectA))
        );
0 голосов
/ 30 мая 2019

Вы можете отобразить коллекцию предметов из коллекции dto или другого класса в.

public Order Convert(OrderDto orderDto)
{
    var order = new Order { OrderLines = new OrderLines() };
    order.OrderLines = Mapper.Map<List<OrderLine>>(orderDto.Positions);
    return order;
}

И ваш конструктор класса профиля может быть написан таким образом. Это всего лишь пример. Вам не нужно принимать список в вашем резольвере, вы можете сделать это для одного объекта и сопоставить список снаружи.

    public Profile()
    {
      CreateMap<PositionDto, OrderLine>()
        .ForMember(dest => dest, opt => opt.ResolveUsing<OrderResolver>());
    }
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...