-
Notifications
You must be signed in to change notification settings - Fork 4
Mutators vs AutoMapper
AutoMapper was conceived as a tool that automatically infers mapping rules in most cases. In complex cases it allows to set rules manually:
public class Source
{
public string UserName { get; set; }
public SourceItem[] Array { get; set; }
}
public class SourceItem
{
public int Value { get; set; }
}
public class Dest
{
public string Login { get; set; }
public DestItem[] Array { get; set; }
}
public class DestItem
{
public int Value { get; set; }
}
...
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.Login, opt => opt.MapFrom(s => s.UserName));
cfg.CreateMap<SourceItem, DestItem>();
});
In the example above the AutoMapper infers that Array
property should be mapped to the corresponding array in the Dest
class (Value
to Value
).
We only should instruct it to automatically infer mapping rules for SourceItem
and DestItem
.
When property names are considerably different the AutoMapper fails to infer the rule, but it allows to set a rule explicitly.
In the example above we specify that the UserName
property should be mapped to the Login
property.
All mapping (conversion) rules should be set explicitly.
configurator.Target(dest => dest.Name).Set(source => source.Name);
In the case of nested classes you should provide full paths to properties:
configurator.Target(dest => dest.User.Name).Set(source => source.UserInfo.Login);
If you need to map an array, you can use Each
and Current
methods:
configurator.Target(dest => dest.Array.Each().Value).Set(source => source.Array.Current().Value);
The Set
method can receive almost any correct C# expression:
configurator.Target(dest => dest.Array.Each().Value)
.Set(source => source.Array.Where(x => x.IntValue == 5).Current().Value);
configurator.Target(dest => dest.StringArray.Each().Value)
.Set(source => new []{"x", "y", "z"}.Current());
A conversion rule can be set for a concrete array item:
configurator.Target(dest => dest.IntArray[0]).Set(source => source.IntValue);
public class Source
{
public Tuple<string, string> First { get; set; }
public Tuple<string, string> Second { get; set; }
}
public class Dest
{
public KeyValuePair<string, string> A { get; set; }
public KeyValiePair<string, string> B { get; set; }
}
Suppose we want to map First => A
and Second => B
.
But in the first case we need to map Item1 => Key
and Item2 => Value
, in the second case we need to map Item1 => Value
and Item2 => Key
.
In the AutoMapper's paradigm mapping rules exist in a context of two types for which we define a mapping.
It means we can define a mapping between Tuple<string, string>
and KeyValuePair<string, string>
for the first case.
But there is no way to define another mapping between theese types for the second case.
However, this is not a problem for Mutators:
configurator.Target(dest => dest.A.Key).Set(source => source.First.Item1);
configurator.Target(dest => dest.A.Value).Set(source => source.First.Item2);
configurator.Target(dest => dest.B.Key).Set(source => source.Second.Item2);
configurator.Target(dest => dest.B.Value).Set(source => source.Second.Item1);
Type checking happens in runtime. The AutoMapper can figure out type cast for simple cases, but it crashes in runtime when it can't.
var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Dest>()
.ForMember(d => d.IntValue, opt => opt.MapFrom(s => s.StringValue))
);
Type checking happes in compile time:
configurator.Target(x => x.IntValue).Set(y => y.StringValue); // compile error
configurator.Target(x => x.IntValue).Set(y => int.Parse(y.StringValue)); // ok
Both Mutators and AutoMapper allow to set conditional mapping rules.
var valueConfigurator = configurator.Target(dest => dest.Value);
valueConfigurator.If(source => source.TakeFromX)
.Set(source => source.X);
valueConfigurator.If(source => !source.TakeFromX)
.Target(dest => dest.Y);
The same construction can be written usign AutoMapper, but it does not work as you expect:
config.CreateMap<Source, Dest>()
.ForMember(d => d.Value, opt =>
{
opt.Condition(s => s.TakeFromX);
opt.MapFrom(s => s.X);
})
.ForMember(d => d.Value, opt =>
{
opt.Condition(s => !s.TakeFromX);
opt.MapFrom(s => s.Y);
});
It turns out only the last mapping rule for a single member will be applied, so no conversion will happen in case when s.TakeFromX
is true
.
-
Mutators interface is more fluent, less nested and less stuffed with generic type arguments.
-
The AutoMapper is more about mapping between pairs of types. Mutators are more about document structure. You cannot define more than one ruleset for a pair of types using the AutoMapper.
-
Mutators' interface forces compile time type checking.
-
Mutators allow to set multiple conditional mappings for the same member of a type.
-
Mutators provide additional features like document validation and path migration.