The book continues from the above. In the previous article, we discussed two ways to use AutoMapper to implement 1-1 mapping between types - Convention and Configuration, and learned how to perform simple OO Mapping. In the last article of this series I want to discuss some mid-level topics based on our needs, including: how to implement mapping between type body types, and how to implement multiple mapping rules for two types.
[4] Mapping a type to a type system
First review our Dto and Model. We have BookDto, we have Author, and each Author has its own ContactInfo. Now a question: How to get the Author object of the first author from BookDto? The answer is simple, but not simple.
The simplest way is to use the CountructUsing mentioned earlier to specify the mapping of all fields and subtype fields from BookDto to Author:
C# code
var map = Mapper.CreateMap
This approach can work, but it is very uneconomical. Because we are doing the mapping from BookDto to Author from scratch, and the mapping from BookDto to ContactInfo has been implemented before, there is really no need to write it again. Imagine that if there is another Reader type that also contains ContactInfo, when mapping BookDto to Reader, should we write the BookDto -> ContactInfo logic again? Imagine again if we implement the mapping from BookDto to Book, do we need to write the mapping rules from BookDto to Author again?
So I think that for this kind of mapping between type systems, the ideal approach is to specify a simple mapping for each specific type, and then reuse the mapping of the simple type when mapping complex types. Describe it in simpler language:
We have four types A, B, C, and D, where B = [C, D]. Given that A -> C, A -> D, find A -> B.
My solution is to use IValueResolver provided by AutoMapper. IValueResolver is a type defined by AutoMapper to implement specific mapping logic at the field level. Its definition is as follows:
C# code
public interface IValueResolver
{
ResolutionResult Resolve(ResolutionResult source);
}
And In actual applications, we often use its generic subclass - ValueResolver, and implement its abstract method:
C# code
protected abstract TDestination ResolveCore(TSource source);
where TSource is the source type , TDestination is the type of the target field.
Back to our example, we can now map BookDto like this -> Author:
C# code
var map = Mapper.CreateMap
map.ForMember(d => d.Name, opt => opt.MapFrom(s => s.FirstAuthorName))
.ForMember(d => d.Description, opt => opt.MapFrom(s => s.FirstAuthorDescription) )
.ForMember(d => d.ContactInfo,
opt => opt.ResolveUsing
In FirstAuthor In ContactInfoResolver, we implement ValueResolver and reuse the logic of BookDto -> ContactInfo :
C# code
public class FirstAuthorContactInfoResolver : ValueResolver
{
protected override ContactInfo ResolveCore(BookDto source)
, ” ” ” ” ” ” ” ” ” ” ” ” ” ” Return ”Mapper.Map
}
}
Everything is done.Similarly, we can now implement BookDto -> Book, right? By reusing BookDto -> Author and BookDto -> Publisher.
Is it really possible? There seems to be still a problem. Yes, we will find that we need to map from BookDto to two different Authors, and their field mapping rules are different. what to do? Let’s quickly move on to our last topic.
[5] Implement multiple sets of mapping rules for two types
Our problem is: for types A and B, we need to define 2 different A -> B and allow them to be used at the same time. In fact, the current AutoMapper does not provide a ready-made way to do this.
Of course we can use the "curve to save the country" method - define two subclasses of Author for the first author and second author, such as FirstAuthor and SecondAuthor, and then implement the BookDto -> FirstAuthor and BookDto -> SecondAuthor mapping respectively. But this method is also not very economical. What if there is a third author or even a fourth author? Define an Author subclass for each author?
On the other hand, we might as well assume that if AutoMapper provided such a function, what would it look like? The CreateMap method and the Map method should be defined like this:
C# code
CreateMap
Map
When we use it, we can:
C# code
var firstAuthorMap = Mapper.CreateMap
// Define BookDto -> first Author rule
var secondAuthorMap = Mapper.CreateMap
// Define BookDto -> second Author rule
var firstAuthor = Mapper.Map< ;BookDto, Author>(source, "first");
var secondAuthor = Mapper.Map
Unfortunately, this is all if. But it doesn't matter, although AutoMapper closed this door, it left another door for us - MappingEngine.
MappingEngine is the mapping execution engine of AutoMapper. In fact, there is a default MappingEngine in Mapper. When we call Mapper.CreateMap, we write rules in the Configuration corresponding to this default MappingEngine, and then call Mapper.Map to obtain the object. Sometimes, the default MappingEngine is used to execute the rules in its corresponding Configuration.
In short, a MappingEngine is a "virtual machine" of AutoMapper. If we start multiple "virtual machines" at the same time and put different mapping rules for the same pair of types on different "virtual machines", we can make They each run peacefully. When using them, just ask the corresponding "virtual machine" which rule to use.
Just do it. First we define a MappingEngineProvider class and use it to obtain different MappingEngines:
C# code
public class MappingEngineProvider
{
private readonly MappingEngine _engine;
public MappingEngine Get()
{
return _engine;
}
}
We abstract different types of mapping rules into interface IMapping:
C# code
public interface IMapping
{
void AddTo( Configuration config);
}
Then put the required rules into the corresponding MappingEngine in the constructor of MappingEngineProvider:
C# code
private static Dictionary
public MappingEngineProvider(Engine engine)
{
var config = new Configuration(new TypeMapFactory(), MapperRegistry.AllMappers());
_rules[ engine].ForEach(r=> r.AddTo(config));
_mappingEngine = new MappingEngine(config);
}
Note that here we use an enumeration type Engine to identify possible MappingEngines:
C# code
public enum Engine
{
Basic = 0,
First,
Second
}
We use 3 Engines, Basic is used to place all Dto - > FirstXXX rules, Second is used to place all Dto -> SecondXXX rules.
We also define a dictionary _rule that places all mapping rules, and classify the rules into different Engines.
The only thing left is to fill in the dictionary_rule with our mapping. For example, we put BookDtoToFirstAuthorMapping in the First engine and BookDtoToSecondAuthorMapping in the Second engine:
C# code
private static readonly Dictionary
new Diction ary
K New BookdtotoFirstAuthormapping (),
}}, { Engine.Second, New List & LT; ImApping & GT; }}},};Of course, for convenience of use, we can instantiate different MappingEngineProvider objects in advance:
C# code public static SimpleMappingEngineProvider First = new MappingEngineProvider(Engine.First); public static SimpleMappingEngineProvider Second = new MappingEngine Provider(Engine.Second) ;Now we can use these 2 Engines at the same time when mapping BookDto -> Book to get 2 Authors and assemble them into the field Book.Authors:
C# code
private class AuthorsValueResolver : ValueResolver
: new List
Finally, do you still remember the good wishes we mentioned at the beginning of this section? Since AutoMapper did not help us implement it, let us implement it ourselves:
C# code
public class MyMapper
{
private static readonly Dictionary
{
TSource source, Engine engine = Engine.Basic) { return Engines[engine].Map
Everything is back, we can do this:
C# code var firstAuthor = MyMapper.Mapcan also be like this:
C# code
var book = MyMapper.Map