0

I am reading the coordinate values from a file using 3rd tool, and tool casts data to a Point class collection.

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

And I want to map Point class to PointEntity using automapper.

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

I created a Map using Automapper.

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

And I have created a generic mapper extension to map List.

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>();

But sometimes users may have wrong type entries in file like following.

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

In this situation, the code throw exception.

So how can I find which row and value is wrong type? (For example row1 and Lon value is wrong)

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
barteloma
  • 6,403
  • 14
  • 79
  • 173

1 Answers1

0

Change your design thinking. Mapping is simply transferring one data to another data. And automapper is very good at that task. Keep it simple. So, keep that as simple as possible. I read that you want to perform a validation during mapping and provide information about it. That make mapping more complex. In my design I wouldn't let this happen during mapping. Validate this information before mapping. Iterate this before mapping by using an validation rule class. Or validates the data at the time of reading so that you can immediately inform a line and column position.

Example

Use the ConvertUsing (but not needed if validated before) Step to Implementation

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);
    }

Implementation

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);
}

Validator

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);
    }
}

Why using a delegate? Other example by collecting all (also for each parameter [lon, lat, ...]) invalide parameters. Here see now the power using a handler for it.

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);
}
Roberto B
  • 542
  • 5
  • 13
  • Do you mean create a method named Validate in Point class. And try parse properties. This is a manuel way. – barteloma May 25 '21 at 10:41
  • is the implementation part a seperated class? – barteloma May 27 '21 at 13:10
  • You ask, "is the implementation part a separate class?" My first reaction is a blind yes. Because it is always better to have as small building blocks as possible and to do things where they belong. But after that I would say it depends on the situation. What do you do in this context. I gave an implementation based on your starting question. (I did write the example in a class and cut it out for this answer. See constructor part: public HowToSpecifyAutomapperExceptionCausedData(IMapper mapper)) – Roberto B May 31 '21 at 08:09