AutoMapper mapping using interface vs concrete map
Asked Answered
S

1

6

I don't think this is possible, but it's worth the question, I suppose.

I have the following types that share an interface (I promise, this isn't the same question I've asked before).

public interface ICustomer;

public class CustomerBO : ICustomer

public class CustomerSO : ICustomer // SO is Service Object in this case.

Then, I have the following mapping:

Mapper.Map<ICustomer, ICustomer>();

Now, here's where it gets interesting / confusing.

This works:

Mapper.Map<ICustomer, ICustomer>(customerSO, new CustomerBO);

This doesn't work:

Mapper.Map(customerSO, new CustomerBO());

Now, normally I wouldn't have a problem with just typing in the first Map statement with the two interface type defined, but my problem is when the Customer object is buried somewhere.

public class CustomerOrderDTO
{
    ICustomer customer;
}

public class CustomerOrderSO
{
    CustomerSO customer;
}

Mapper.Map<CustomerOrderDTO, CustomerOrderSO>();

This doesn't work, because there's no mapping from ICustomer to CustomerSO, so config assertion fails.

Currently, I'm going around the issue by doing this:

Mapper.CreateMap<CustomerOrderDTO, CustomerOrderSO>()
    .ForMember(desc => dest.customer
        , exp => exp.MapFrom(src => Mapper.Map<ICustomer, ICustomer>(src.customer
            , new CustomerSO));

However, I would have to do this for every DTO-type object that we have, and then quite possibly have a cascading effect.

I understand that technically I could do the following to resolve the issue:

Mapper.Map<CustomerBO, CustomerSO>();

However, in CustomerBO there are a lot of other properties used in the business logic not in the interface. Similarly, there are a lot of properties in CustomerSO not in the interface. If I were to go with the above route, I would have a ton of Ignore() calls, and I'd have to map CustomerBO to CustomerSO, and then CustomerSO to CustomerBO, each with their own unique list of Ignore calls. Using the interfaces removes the need for the Ignore calls, as the data that I want to be visible from one to the other is defined in the interface.

So, in short, my question is this: is there some way I can tell AutoMapper to use the interface map when it encounters one of the implementing classes? Failing that, is there some other (read: better) way than a Map call in a MapFrom delegate to enforce my interface-to-interface mapping in a as-needed basis?

Sermon answered 6/9, 2012 at 20:20 Comment(4)
I've been thinking about your question. It's a good question but I doubt if you should go this path. The thing is: by mapping objects as interfaces you're creating incomplete objects. That means that you should always be aware where an object comes from and/or write logic all over the place to check whether properties are null. AutoMapper was made to facilitate working with DTO's. I consider incomplete DTO's an anti-pattern (although it is tempting to reuse them and compromise). So e.g. your CustomerOrderDTO should contain a dedicated CustomerDTO, not an interface. Well - my opinion :).Mychael
Unfortunately, the DTOs were only an example. We do not make use of DTOs. Instead, the objects from our three layers share an interface, or interfaces. The BOs have factories that can accept interfaces (which I then use to map after validation). BOs make requests from the DAL, which return the appropriate interface (if needed) and then those are mapped to the existing BOs. There's no library of DTOs used to transfer data between the layers. I see the advantage of using them, but it's currently out of the scope of time I have for this refactorization / rewrite.Sermon
I'm not sure this assertion is always true, "by mapping objects as interfaces you're creating incomplete objects", and especially so for DTOs. I mean, if you are strict about an Interface describing behavior (and thus, no state), then sure, your objects would be incomplete, but only because they would be empty of any properties, having only methods. As a DTO, an object with only methods is useless. Moreover, if class MyDTO : Imydto { } and interface Imydto { Imyprop myprop {get;set;} }, then MyDTO MUST contain public Imyprop myprop {get;set;}.Leflore
(Outside of purist design ideology, I don't see any fundamental roadblocks or problems this creates. Some good examples of where this may be desired: 1) cross-domain applications where versioned, lightweight interfaces are preferable. 2) trying to spoof multiple inheritance, like if you want to perform common dynamic actions across specific subsets of an Entity Framework Model. 3) Loading data via MEF. )Leflore
R
3

In general, you'd want to configure a mapping to the ICustomer interface and override the destination type using .As<TDestination>(). (Maybe this is an addition since you first asked the question.)

This would work for mappings between 2 layers, like your example:

Mapper.CreateMap<CustomerDTO, ICustomer>().As<CustomerModel>();
Mapper.CreateMap<CustomerModel, ICustomer>().As<CustomerDTO>();

However, extend this to another layer, such as mapping CustomerModel to CustomerViewModel, and this falls apart. You can only tell AutoMapper one destination type to use for instantiation.

But similar to the comments above, I would question the reason for having a property typed as the interface within those other classes. Why should a CustomerOrderModel have a ICustomer customer property? Can you replace customer with an instance of a CustomerDTO and still have your program be correct? If you have common behavior for objects that contain a customer, you could have those parent objects implement an interface, ICustomerAccessor. Then, use explicit interface implementation to add the weaker-typed property, and it won't have to interfere with the name.

Resource answered 25/11, 2015 at 21:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.