Automapper quandry C#

Solarion

Honorary Master
Joined
Nov 14, 2012
Messages
28,051
Reaction score
17,804
Hi guys. I've hit an AutoMapper snag and no way to resolve this so far. I am trying to map a domain entity to a dto and then return the list as a view model.

I feel like I am missing a step somewhere. With EF we can simply ProjectTo as such.

C#:
var employees = await _context.Employees
.ProjectTo<EmployeeDto>(_mapper.ConfigurationProvider)
.ToListAsync(cancellationToken);

var vm = new EmployeeListVm
{
    Employees = employees
};

return vm;

No such like though when trying this without Entity Framework.

This is the error I'm getting.

AutoMapperConfused.jpg

This is the Mapping.

C#:
public interface IMapFrom<T>
{
    void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}

C#:
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var methodInfo = type.GetMethod("Mapping");
            methodInfo?.Invoke(instance, new object[] { this });
        }
    }
}

Please can someone help fill in a piece of a puzzle that I am missing somewhere.
 
you have not initialised Automapper in your console app.

you have to at some point call

Code:
var source = ....
var target = _mapper.Map(source)

Also if you want to assign like you are doing there, you are also going to need to create implicit operator, that somehow as access to automapper - this should obviously be avoided as this just makes for confusing code

This is the same reason you cannot say

Code:
string myValue = null;
myValue = 123;

but you CAN do the above, if you create a new implicit operator on "string"
 
Last edited:
you have not initialised Automapper in your console app.

you have to at some point call

Code:
var source = ....
var target = _mapper.Map(source)

Also if you want to assign like you are doing there, you are also going to need to create implicit operator, that somehow as access to automapper - this should obviously be avoided as this just makes for confusing code

This is the same reason you cannot say

Code:
string myValue = null;
myValue = 123;

but you CAN do the above, if you create a new implicit operator on "string"

Great thanks, got it working. I'll post the source shortly.

I dumped the console app, went for a blank weatherforecast API. Initialized (injected) the automapper.

Only thing I did differently to get ProjectTo working was to append .AsQueryable() on the end of employees.
 
Working. Thanks @_kabal_

Of course you probably wouldn't do this in the controller, but for demo purposes.

Screenshot_1.jpg

C#:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IMapper _mapper;

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

    [HttpGet]
    public IActionResult Get()
    {
        var entities = new List<Employee>
        {
            new Employee { FirstName = "John", LastName = "Smith" },
            new Employee { FirstName = "Tim", LastName = "Curry" }
        }.AsQueryable();

        var employees = _mapper.ProjectTo<EmployeeDto>(employees).ToList();

        var vm = new EmployeesListVm
        {
            Employees = employees.ToList()
        };

        return Ok(vm);
    }
}

C#:
public class EmployeeDto: IMapFrom<Employee>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public void Mapping(Profile profile)
    {
        profile.CreateMap<Employee, EmployeeDto>();
    }
}

C#:
public class EmployeesListVm
{
    public IList<EmployeeDto> Employees { get; set; }
}

C#:
public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

C#:
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAutoMapper(Assembly.GetExecutingAssembly());
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

C#:
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
    }

    private void ApplyMappingsFromAssembly(Assembly assembly)
    {
        var types = assembly.GetExportedTypes()
            .Where(t => t.GetInterfaces().Any(i =>
                i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
            .ToList();

        foreach (var type in types)
        {
            var instance = Activator.CreateInstance(type);
            var methodInfo = type.GetMethod("Mapping");
            methodInfo?.Invoke(instance, new object[] { this });
        }
    }
}

C#:
public interface IMapFrom<T>
{
    void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
 
Great thanks, got it working. I'll post the source shortly.

I dumped the console app, went for a blank weatherforecast API. Initialized (injected) the automapper.

Only thing I did differently to get ProjectTo working was to append .AsQueryable() on the end of employees.
why bother with that?

Code:
var employees = new List<Employee>
        {
            new Employee { FirstName = "John", LastName = "Smith" },
            new Employee { FirstName = "Tim", LastName = "Curry" }
        };

var employeeDtos = _mapper.Map<List<EmployeeDto>>(employees);

'ProjectTo' is definitely useful, but it has limitations (e.g., does not support mapping "Conditions") - https://docs.automapper.org/en/latest/Queryable-Extensions.html#supported-mapping-options

Also, your object that was an IQueryable then became a list, and then became an IQueryable again. You should probably check your EF logs, because it "might" actually be doing a database query again, if it is still connected to EF Core with change tracking.

Internally, ProjectTo is doing things like

`SELECT
Name as CustomerName //Name is the Entity fieldname, CustomerName is the DTO fieldname
FROM
Customer
`
 
Last edited:
I also use automapper to map my domain entities to dtos. Will post some code but essentialy created an object extension method so i can can:
mydto = mydomainEntity.MapTo<MyDto>();
and then use linq

you were trying to cast a generic list to another with different generic arg. You can use Cast<> for that If the objects have cast operator overloads.

automapper is great for creating projections
but not sure why you call the return type a viewmodel. Is is more a model than a viewmodel. The viewmodel is more of a construct that allows a view to bind to a model and where the bahaviour sits

EDIT:

Code:
  public static class Mapping
  {
    public static TDestination MapTo<TDestination>(this object from)
      where TDestination : class
    {
      Ensure.ArgumentIsNotNull(from, nameof(from));

      Type fromType = from.GetType();
      Type destinationType = typeof(TDestination);

      MapperConfiguration config = new MapperConfiguration(cfg => cfg.CreateMap(fromType, destinationType));

      TDestination result = new Mapper(config).Map<TDestination>(from);
      
      return result;
    }
  }
 
Last edited:
Top
Sign up to the MyBroadband newsletter
X