Выровнять вложенный объект - PullRequest
0 голосов
/ 15 мая 2018

Я использую AutoMapper 6.2.2, у меня есть две исходные модели с общим свойством Id:

using System.Diagnostics;
using AutoMapper;

public class Outer
{
    public int Id { get; set; }
    public string Foo { get; set; }
    public Inner Bar { get; set; }
}
public class Inner
{
    public int Id { get; set; }
    public string Baz { get; set; }
    public string Qux { get; set; }
    public string Bof { get; set; }
}
public class FlatDto
{
    public int Id { get; set; }
    public string Foo { get; set; }
    public string Baz { get; set; }
    public string Qux { get; set; }
    public string Bof { get; set; }
}
public class AutoMapperProfile : Profile
{
    public AutoMapperProfile()
    {
        this.CreateMap<Outer, FlatDto>()
            .ForMember(dst => dst.Id, opt => opt.MapFrom(s => s.Id))
            .ForMember(dst => dst.Foo, opt => opt.MapFrom(s => s.Foo))
            .ForMember(dst => dst.Baz, opt => opt.MapFrom(s => s.Bar.Baz))
            .ForMember(dst => dst.Qux, opt => opt.MapFrom(s => s.Bar.Qux))
            .ForMember(dst => dst.Bof, opt => opt.MapFrom(s => s.Bar.Bof));
    }
}
class Program
{
    static void Main(string[] args)
    {
        Outer model = new Outer
        {
            Id = 1,
            Foo = "FooString",
            Bar = new Inner
            {
                Id = 2,
                Baz = "BazString",
                Qux = "QuxString",
                Bof = "BofString"
            }
        };

        var config = new MapperConfiguration(cfg => cfg.AddProfiles(typeof(Program).Assembly));
        config.AssertConfigurationIsValid();
        IMapper mapper = new Mapper(config);

        FlatDto dto = mapper.Map<Outer, FlatDto>(model);
        Trace.Assert(model.Id == dto.Id);
        Trace.Assert(model.Foo == dto.Foo);
        Trace.Assert(model.Bar.Baz == dto.Baz);
        Trace.Assert(model.Bar.Qux == dto.Qux);
        Trace.Assert(model.Bar.Bof == dto.Bof);
    }
}

Я хочу, чтобы FlatDto.Id пришел от Outer, а остальные параметры - по имени. Соглашение AutoMapper в этом случае довольно ясно, однако я не могу изменить эти свойства. В настоящее время он явно отображается с ForMember для каждого свойства dest. Решение для похожего вопроса на самом деле еще длиннее.

Существует ли более элегантное решение для этого случая, когда обе модели содержат несколько полей и только одно перекрывается и требует явной обработки?

1 Ответ

0 голосов
/ 15 мая 2018

Самое простое решение (без изменения кода, даже если вы изменили Внешнее / Внутреннее в будущем):

Mapper.Initialize(c =>
{
    c.CreateMap<Inner, FlatDto>();
    c.CreateMap<Outer, FlatDto>().BeforeMap((s, t) => Mapper.Map(s.Bar, t));
});

Помните, что:

  1. Вам необходимоизменить, если вы используете сопоставители экземпляров вместо статического .Map метода "глобального" сопоставления.

  2. Свойства с одинаковыми именами в Inner и Outer будут отображаться дважды,и Outer имеет более высокий приоритет, будьте осторожны с возможными побочными эффектами.

EDIT Поскольку вы используете средства отображения и профили экземпляров, экземпляр IMapperвнутри профиля невозможно получить доступ, нам нужно зарегистрировать динамические отображения.Следующий код, как и фрагмент кода в вопросе, по существу использует .ForMember с аргументами, встроенными в динамические выражения.

class TestProfile : Profile
{
    public TestProfile()
    {
        BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
        Func<PropertyInfo, bool> filter = p => p.CanRead && p.CanWrite;

        var outerProperties = typeof(Outer).GetProperties(flags).Where(filter).ToDictionary(p => p.Name);
        var innerProperties = typeof(Inner).GetProperties(flags).Where(filter);
        var mappingProperties = innerProperties.Where(p => !outerProperties.ContainsKey(p.Name));

        //code above gets the properties of Inner that needs to be mapped

        var outerParameter = Expression.Parameter(typeof(Outer));
        var accessBar = Expression.Property(outerParameter, nameof(Outer.Bar));
        var map = CreateMap<Outer, FlatDto>();
        var mapExp = Expression.Constant(map);

        foreach (var property in mappingProperties)
        {
            var accessProperty = Expression.MakeMemberAccess(accessBar, property);
            var funcType = typeof(Func<,>).MakeGenericType(typeof(Outer), property.PropertyType);
            var funcExp = Expression.Lambda(funcType, accessProperty, outerParameter);
            //above code builds s => s.Bar.Qux

            var configType = typeof(IMemberConfigurationExpression<,,>).MakeGenericType(typeof(Outer), typeof(FlatDto), typeof(object));
            var configParameter = Expression.Parameter(configType);
            var mapFromMethod = configType
                .GetMethods()
                .Single(m => m.Name == "MapFrom" && m.IsGenericMethod)
                .MakeGenericMethod(property.PropertyType);
            var invokeMapFrom = Expression.Call(configParameter, mapFromMethod, funcExp);
            var configExp = Expression.Lambda(typeof(Action<>).MakeGenericType(configType), invokeMapFrom, configParameter);
            //above code builds opt => opt.MapFrom(s => s.Bar.Qux)

            var forMemberMethod = map.GetType()
                .GetMethods()
                .Single(m => m.Name == "ForMember" && !m.IsGenericMethod);
            var invokeForMember = Expression.Call(mapExp, forMemberMethod, Expression.Constant(property.Name), configExp);
            //above code builds map.ForMember("Qux", opt => opt.MapFrom(s => s.Bar.Qux))

            var configAction = Expression.Lambda<Action>(invokeForMember);
            configAction.Compile().Invoke();
        }
    }
}

Выглядит очень большой код, но на самом деле вы можете (и должны) поставитьво фрагменте get свойства / метода где-то еще, сам цикл foreach использует их для построения выражения для вызова.Это довольно чисто и эффективно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...