简体   繁体   中英

How can I use Automapper to map an object to an unknown destination type?

Consider the following scenario. I have a number of classes that share a common base class and I have defined an automapper mapping for each derived class. Something like this:

class A : Base {}

class B : Base {}

class ContractA : ContractBase {}

class ContractB : ContractBase {}

void Foo()
{
    Mapper.CreateMap<A, ContractA>();
    Mapper.CreateMap<B, ContractB>();
}

So far so good. But now I want to create a method like this:

ContractBase Foo()
{
    Base obj = GetObject();

    return Mapper.???
}

The problem is that all of AutoMapper's Map variants require that I either know the destination type at compile time or have an object of that type available at runtime. This is seriously frustrating since I have defined only one map for each source type. AutoMapper should be able to infer the destination type given only the source type.

Is there any good way around this? I want to avoid creating a dictionary mapping source types to destination types. While this would work, it would mean that I'd essentially have to define two mappings for every source type.

You could access the mappings stored in AutoMapper:

ContractBase Foo() {
  Base obj = GetObject();

  var sourceType = obj.GetType();
  var destinationType = Mapper.GetAllTypeMaps().
    Where(map => map.SourceType == sourceType).
    // Note: it's assumed that you only have one mapping for the source type!
    Single(). 
    DestinationType;

  return (ContractBase)Mapper.Map(obj, sourceType, destinationType);
}

You can turn it around and ask Base to give you a mapped contract:

ContractBase Foo() {
  Base obj = GetObject();
  return obj.ToContract();
}

With this code:

abstract class Base {
  public abstract ContractBase ToContract();
}
class A : Base {
  public override ContractBase ToContract() {
    return Mapper.Map<A, ContractA>(this);
  }
}
class B : Base {
  public override ContractBase ToContract() {
    return Mapper.Map<B, ContractB>(this);
  }
}

UPDATE: if you must separate the logic from the classes, you could use a visitor:

ContractBase Foo() {
  Base obj = GetObject();
  var visitor = new MapToContractVisitor();
  obj.Accept(visitor);
  return visitor.Contract;
}

This is what it looks like:

abstract class Base {
  public abstract void Accept(IBaseVisitor visitor);
}
class A : Base {
  public override void Accept(IBaseVisitor visitor) {
    visitor.Visit(this);
  }
}
class B : Base {
  public override void Accept(IBaseVisitor visitor) {
    visitor.Visit(this);
  }
}
interface IBaseVisitor {
  void Visit(A a);
  void Visit(B b);
}
class MapToContractVisitor : IBaseVisitor {
  public ContractBase Contract { get; private set; }
  public void Visit(A a) {
    Contract = Mapper.Map<A, ContractA>(a); 
  }
  public void Visit(B b) {
    Contract = Mapper.Map<B, ContractB>(b);
  }
}

Now, all the mapper logic is in the MapToContractVisitor class, not in the Base hierarchy classes.

我认为您正在寻找Mapper.DynamicMap()及其各种重载。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM