Automapper не работает с .MapFrom на троичном и вычисленном значении с Net Core 2.2

я использую;

AutoMapper.Extensions.Microsoft.DependencyInjection 6.0.0

в проекте веб-API, запущенном net core 2.2

при сопоставлении моего объекта DTO я использую Automapper для сопоставления нескольких полей;

public class AutoMapperProfile : AutoMapper.Profile
{
    public AutoMapperProfile()
    {     
        CreateMap<ReviewPostInputModel, Review>()
            .ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.MapFrom(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.ReceiveUpdates, opt => opt.MapFrom(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.AverageScore, opt => opt.MapFrom(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
        // ...
    }
}

Где;

using System;
using System.Collections.Generic;
using System.Text;

public class Review 
{
    // ...

    public decimal Reliability { get; set; }
    public decimal Tidiness { get; set; }
    public decimal Courtsey { get; set; }
    public decimal Workmanship { get; set; }

    public decimal AverageScore { get; set; }
    public DateTime? ReceiveUpdates { get; set; }
    public DateTime? ReceiveThirdPartyUpdates { get; set; }
} 

Однако, когда я пытаюсь сопоставить с помощью;

var review = _mapper.Map<Review>(model);

Все стандартные элементы сопоставляются, кроме моего ForMember, указанного выше, где DateTimes установлен на новый экземпляр DateTime, а Averagescore установлен на 0.

Для полноты я ввожу преобразователь в свой контроллер следующим образом:

private readonly IMapper _mapper;

public ReviewController( IMapper mapper)
{
    _mapper = mapper;
}

Я настраиваю Automapper в своем StartUp.cs следующим образом;

services.AddAutoMapper();

Я также попытался добавить тест к контроллеру, чтобы подтвердить, что значения из ввода не являются проблемой (завершено после карты и может подтвердить, что это значение правильно обновлено);

review.AverageScore = (decimal)Math.Round((model.Courtsey + model.Reliability + model.Tidiness + model.Workmanship) / 4, 2);

У кого-нибудь есть идеи, почему это происходит?


person Matthew Flynn    schedule 11.04.2019    source источник
comment
извините .. вы вызываете AutoMapperProfile() в startUp?   -  person federico scamuzzi    schedule 11.04.2019
comment
В моем методе запуска ConfigureServices у меня services.AddAutoMapper(); все стандартные сопоставления работают. Это как раз эти крайние случаи, которые не являются.   -  person Matthew Flynn    schedule 11.04.2019
comment
Итак, я только что попытался воспроизвести вашу проблему безрезультатно. Я следил за этим блогом: projectcodify.com/using-automapper-in-aspnet-core , чтобы настроить проект .NET Core 2.2 на использование AutoMapper.Extensions.Microsoft.DependencyInjection, и скопировал вашу модель и профиль преобразователя, но все свойства сопоставляются. Вы уверены, что ваша входная модель имеет ненулевые значения для Courtsey, Reliability? Кроме того, что вы подразумеваете под новым DateTime? Вы имеете в виду ненулевое значение? Вы используете DateTime.UtcNow для его заполнения, что не обязательно будет вашим местным временем.   -  person Adrian Sanguineti    schedule 11.04.2019
comment
Спасибо, что нашли время воспроизвести, я уверен, что передал значения, поскольку я вручную добавил тестовую строку, вычисляющую среднее значение, чтобы я мог отладить этот расчет. Под новым DateTime я подразумеваю, что получаю {01/01/0001 00:00:00} для значения вместо DateTime.UTCNow или Null   -  person Matthew Flynn    schedule 11.04.2019


Ответы (2)


Вам нужно использовать «ResolveUsing», а не «MapFrom».

public class AutoMapperProfile : AutoMapper.Profile
{
    public AutoMapperProfile()
    {     
        CreateMap<ReviewPostInputModel, Review>()
            .ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.ResolveUsing(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.ReceiveUpdates, opt => opt.ResolveUsing(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
            .ForMember(x => x.AverageScore, opt => opt.ResolveUsing(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
        // ...
    }
}

Вы можете посмотреть этот ответ: AutoMapper: в чем разница между MapFrom и ResolveUsing?

У нас была проблема в прошлом при добавлении автоматического сопоставления из «ConfigureServices», и, возможно, это то же самое для вас. Можно попробовать поставить это:

AutoMapperConfiguration.Init();

в функции запуска и добавьте этот класс:

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;

namespace yournamespace.ViewModels.Mappings
{
    public static class AutoMapperConfiguration
    {
        public static void Init()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<ReviewPostInputModel, Review>()
                .ForMember(x => x.ReceiveThirdPartyUpdates, opt => opt.MapFrom(src => src.ReceiveThirdPartyUpdates ? (DateTime?)DateTime.UtcNow : null))
                .ForMember(x => x.ReceiveUpdates, opt => opt.MapFrom(src => src.ReceiveUpdates ? (DateTime?)DateTime.UtcNow : null))
                .ForMember(x => x.AverageScore, opt => opt.MapFrom(src => (decimal)Math.Round((src.Courtsey + src.Reliability + src.Tidiness + src.Workmanship) / 4, 2)));
            });
        }
    }
}
person Yair I    schedule 11.04.2019

После некоторого расследования я думаю, что воспроизвел вашу проблему. Я создал базовый веб-сайт ASP.NET Core 2.2, установил AutoMapper.Extensions.Microsoft.DependencyInjection 6.0.0, создал класс Review, который соответствует вашему, и предположил, как выглядит класс ReviewPostInputModel, исходя из вашего определения сопоставления. Затем я добавил класс вашего профиля сопоставления AutoMapperProfile в проект и настроил запуск следующим образом:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddAutoMapper();
    services.AddMvc...
}

Затем я «взломал» сгенерированный по умолчанию HomeController, чтобы проверить сопоставление:


 public class HomeController : Controller
    {
        private IMapper _mapper;

        public HomeController(IMapper mapper)
        {
            _mapper = mapper;
        }

        public IActionResult Index()
        {
            var input = new ReviewPostInputModel();
            input.ReceiveThirdPartyUpdates = true;
            input.Tidiness = 3;
            input.Reliability = 2;
            input.NotDefinedOnProfile = "sss";

            var output = _mapper.Map<Review>(input);

            // Lazy test to avoid changing model.
            throw new Exception($"{output.ReceiveThirdPartyUpdates} - {output.AverageScore} - {output.NotDefinedOnProfile}");

            return View();
        }
...

Теперь это сработало для меня, так как в сообщении об исключении, которое я получил, было 11/04/2019 2:56:31 PM - 1.25 - sss.

Затем я создал другую сборку и переместил в нее класс AutoMapperProfile. Затем я повторно запускаю тест, но получаю следующую ошибку:

AutoMapper.AutoMapperConfigurationException: найдены несопоставленные элементы. Просмотрите типы и члены ниже. Добавьте пользовательское выражение сопоставления, игнорирование, добавьте собственный преобразователь или измените > тип источника/назначения.

Если нет подходящего конструктора, добавьте ctor без аргументов, добавьте необязательные аргументы или сопоставьте все параметры конструктора.

AutoMapper создал эту карту типов для вас, но ваши типы не могут быть сопоставлены с использованием >текущей конфигурации. ReviewPostInputModel -> Review (список участников назначения) ReviewPostInputModel -> Review (список участников назначения)

Несопоставленные свойства: AverageScore ReceiveUpdates NotDefinedOnProfile

Это имеет смысл, поскольку метод services.AddAutoMapper(); ищет в текущей сборке только профили.

Затем я изменил конфигурацию на: services.AddAutoMapper(cfg => cfg.ValidateInlineMaps = false);, чтобы отключить ошибку, и повторно запустить тест.

Новый вывод: 1/01/0001 12:00:00 AM - 0 - sss

Итак, это наводит меня на мысль, что AutoMapper не может найти класс вашего профиля, и в этом случае вы можете настроить его вручную, используя:

 services.AddAutoMapper(cfg =>
 {
    cfg.AddProfile<AutoMapperProfile>();
 });

или вручную определить сборки для поиска, используя одну из других перегрузок, например:

services.AddAutoMapper(param System.Reflection.Assembly[] assemblies);

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

person Adrian Sanguineti    schedule 11.04.2019