1

My application needs to display a table of customer data, including data about the customer and about his most recent order from a given warehouse. The customer domain object contains a GetLatestOrder(warehouseId) method.

I have a CustomerView viewmodel and want to be able to populate it with some fields from the customer object, and a few fields from the latest order object. Can I do this using Automapper?

Paul Taylor
  • 5,651
  • 5
  • 44
  • 68

3 Answers3

4

Try this,

In Global.asax.cs [Application_Start], public static void AppInitialize()

    protected void Application_Start(object sender, EventArgs e)
    {
        Mapper.CreateMap<Order, CustomerViewModel>()
             .ForMember(destination => destination.OrderId, options => options.MapFrom(source => source.Id))
             .ForMember(destination => destination.OrderName, options => options.MapFrom(source => source.Name));
        Mapper.CreateMap<Customer, CustomerViewModel>()
             .ForMember(destination => destination.CustmerId, options => options.MapFrom(source => source.Id))
             .ForMember(destination => destination.CustmerName, options => options.MapFrom(source => source.Name))
             .ForMember(destination => destination.OrderId, options => options.MapFrom(source => Mapper.Map<IEnumerable<Order>, IEnumerable<CustomerViewModel>>(source.Orders).FirstOrDefault().OrderId))
             .ForMember(destination => destination.OrderName, options => options.MapFrom(source => Mapper.Map<IEnumerable<Order>, IEnumerable<CustomerViewModel>>(source.Orders).FirstOrDefault().OrderName));
    }

And in your code call the mapper like below,

        var lstCustomers = new List<Customer>
        {
            new Customer { Id = 1, Name="Name", Orders = new List<Order> { new Order { Id = 1000, Name ="Some Name"}}},
        };

        var result = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerViewModel>>(lstCustomers);

I am presumed that you classes looks like below,

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class CustomerViewModel
{
    public int CustmerId { get; set; }
    public string CustmerName { get; set; }
    public int OrderId { get; set; }
    public string OrderName { get; set; }
}

Also refer this post.

Community
  • 1
  • 1
Prasad Kanaparthi
  • 6,423
  • 4
  • 35
  • 62
  • Thanks @Prasad Kanaparthi. The scenario is very like the one you describe, but there is one crucial difference. The single row from the Orders entity has to be filtered by WarehouseId, and this value is not available during Application_Start, only during controller method execution. – Paul Taylor Nov 13 '12 at 07:07
  • @PaulTaylor Can you please post your requirement with sample code. If i understand correctly, i guess you cannot do filtered by warehouseid in application_start using automapper. instead you can do another way. please post the details. thanks – Prasad Kanaparthi Nov 13 '12 at 08:20
1

Yes, you can:

var vm = new CustomerViewModel();
Mapper.Map(customer, vm);
Mapper.Map(order, vm);

Each mapping will populate the properties it is configured to and leave the rest.

Knaģis
  • 20,827
  • 7
  • 66
  • 80
  • Thanks, yes that works for individual customers, but I need to be able to populate a collection of customers and include details of an order retrieved by calling a method (I need to pass a parameter to the method, so the mapping can't be achieved at Automapper configuration time). Any ideas? – Paul Taylor Nov 12 '12 at 18:24
  • 1
    If you could create that method as static or put it on your Customer class you could use AfterMap at the configuration time. If not, then I'm not sure if you will get through without creating a proxy class that will then be passed to the AutoMapper. – Knaģis Nov 12 '12 at 18:26
  • @Knagís, thanks for the tip about AfterMap, haven't seen that. The method in question is on the Customer class. Will give that a go. But, looking at an example of AfterMap, it seems you have to pass any arguments to the AfterMap delegate at configuration time. Unfortunately, the value to pass is not available until the action method is called in the MVC controller: Mapper.CreateMap() .AfterMap((c, cv) => c.GetLatestOrder(WarehouseId)); – Paul Taylor Nov 12 '12 at 18:38
  • ... continued [Sorry, my edit timed out on the last comment]. The above code will not work in Application_Start, because WarehouseId is not available. I understand from Automapper docs that CreateMap needs to be called during the Application_Start event. – Paul Taylor Nov 12 '12 at 18:46
  • 1
    I would probably just go the KIS way and just iterate through the collection and do the second mapping manually... – Knaģis Nov 12 '12 at 18:51
  • Yep, think I will have to go that way – Paul Taylor Nov 13 '12 at 10:13
1

In the end, I took the easy approach, couldn't find another way:

In Application_start:

    Mapper.CreateMap<Customer, CustomerView>();
    Mapper.CreateMap<Order, CustomerView>();

In the controller method:

    IEnumerable<Customer> customers = GetCustomers(false)
                                               .OrderBy(c => c.Name);
    IEnumerable<CustomerView> viewData = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerView>>(customers);
    foreach (Customer customer in customers)
    {
        CustomerView view = viewData.Where(cv => cv.CustomerId == customer.CustomerId).First();
        Mapper.Map(customer.GetLatestOrder(WarehouseId), view);
    }
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68