Как указать данные, вызванные исключением automapper?

Я читаю значения координат из файла с помощью третьего инструмента, и инструмент передает данные в коллекцию классов Point.

public class Point
{
    public string Lon {get;set;}
    public string Lat {get;set;}
    public string Elevation {get;set;}
}

И я хочу сопоставить класс Point с PointEntity с помощью automapper.

public class PointEntity
{
    public float Lon {get;set;}
    public float Lat {get;set;}
    public float Elevation {get;set;}
}

Я создал карту с помощью automapper.

public class LocationProfile : Profile
{
    public LocationProfile()
    {
        CreateMap<Point, PointEntity>();
    }
}

И я создал универсальное расширение картографа для отображения списка.

public static class MapperHelper
{
    public static TDest MapTo<TDest>(this object src)
    {
        return (TDest)AutoMapper.Mapper.Map(src, src.GetType(), typeof(TDest));
    }
}

IList<Point> data = readData<Point>("file");
var mapped = data.MapTo<PointEntity>();

Но иногда у пользователей могут быть записи неправильного типа в файле, как показано ниже.

Lon    Lat    Elevation
---    ---    ---------
11.5   25.6   80.56
12ab   89.87  14.83
1.7    x.8    9.3

В этом случае код генерирует исключение.

Итак, как я могу найти, какая строка и значение имеют неправильный тип? (Например, значение row1 и Lon неверно)


person barteloma    schedule 25.05.2021    source источник


Ответы (1)


Измените свое дизайнерское мышление. Отображение — это просто перенос одних данных в другие данные. И automapper отлично справляется с этой задачей. Будь проще. Итак, держите это как можно проще. Я читал, что вы хотите выполнить проверку во время сопоставления и предоставить информацию об этом. Это делает отображение более сложным. В моем дизайне я бы не позволил этому случиться во время отображения. Проверьте эту информацию перед сопоставлением. Повторите это перед отображением с помощью класса правила проверки. Или проверяет данные во время чтения, чтобы вы могли сразу сообщить позицию строки и столбца.

Пример

Используйте ConvertUsing (но не требуется, если он был проверен ранее) Шаг к реализации

public class LocationProfile : Profile
{
    public LocationProfile()
    {
        CreateMap<Point, PointEntity>().ConvertUsing<PointEntityConverter>();
    }
}

public class PointEntityConverter : ITypeConverter<Point, PointEntity>
{
    public PointEntity Convert(Point source, PointEntity destination, ResolutionContext context)
    {
        if (destination == null)
            destination = new PointEntity();

        destination.Lon = Parse(source.Lon, nameof(source.Lon));
        destination.Lat = Parse(source.Lat, nameof(source.Lat));
        destination.Elevation = Parse(source.Elevation, nameof(source.Elevation));

        return destination;
    }

    private float Parse(string s, string paramName)
    {
        if (float.TryParse(s, out float result))
            return result;

        throw new ArgumentException($"Invalide value ({s}) for parameter {paramName}", paramName);
    }

Реализация

private readonly IMapper mapper;
private readonly IValidator<Point> validator;

public HowToSpecifyAutomapperExceptionCausedData(IMapper mapper)
{
    this.mapper = mapper;
    this.validator = new PointValidator();
}

public void Example()
{
    var data = readData<Point>("file");

    for (int i = 0; i < data.Count; i++)
        validator.AssertValide(data[i], (m, p) => OnInvalidArgument(m, p, i));

    var mapped = mapper.Map<IList<PointEntity>>(data);
}

private void OnInvalidArgument(string message, string paramName, int index)
{
    throw new ArgumentException($"{message} on index {index}", paramName);
}

Валидатор

internal interface IValidator<T>
{
    void AssertValide(T value, InvalideArgumentHandler handler);
}

internal delegate void InvalideArgumentHandler(string message, string paramName);

internal class PointValidator : IValidator<Point>
{
    public void AssertValide(Point value, InvalideArgumentHandler handler)
    {
        AssertValideFloat(value.Lon, nameof(value.Lon), handler);
        AssertValideFloat(value.Lat, nameof(value.Lat), handler);
        AssertValideFloat(value.Elevation, nameof(value.Elevation), handler);
    }

    private void AssertValideFloat(string s, string paramName, InvalideArgumentHandler handler)
    {
        if (!float.TryParse(s, out float result))
            handler($"Invalide value ({s}) for parameter {paramName}", paramName);
    }
}

Зачем использовать делегат? Другой пример: сбор всех (также для каждого параметра [долгота, широта, ...]) недействительных параметров. Вот теперь посмотрите на силу, использующую для этого обработчик.

public void Example()
{
    var data = readData<Point>("file");

    var errors = new List<string>();

    for (int i = 0; i < data.Count; i++)
        validator.AssertValide(data[i], (m, p) => errors.Add($"{m} on index {i}"));

    if (errors.Any()) //using System.Linq;
        throw new ApplicationException(string.Join(Environment.NewLine, errors));

    var mapped = mapper.Map<IList<PointEntity>>(data);
}
person Roberto B    schedule 25.05.2021
comment
Вы имеете в виду создать метод с именем Validate в классе Point. И попробуй разобрать свойства. Это мануальный способ. - person barteloma; 25.05.2021
comment
является частью реализации отдельным классом? - person barteloma; 27.05.2021
comment
Вы спросите, является ли часть реализации отдельным классом? Моя первая реакция — слепое «да». Потому что всегда лучше иметь как можно меньше строительных блоков и делать то, что им нужно. Но после этого я бы сказал, что это зависит от ситуации. Что вы делаете в этом контексте. Я дал реализацию, основанную на вашем начальном вопросе. (Я написал пример в классе и вырезал его для этого ответа. См. Часть конструктора: public HowToSpecifyAutomapperExceptionCausedData (сопоставитель IMapper)) - person Roberto B; 31.05.2021