I'm mapping an object using AutoMapper and my destination object has already some filled properties at this point.
My Configuration already looks like this:
// MapperConfiguration
CreateMap<TestClass, TestClass>()
.ForMember(d => d.Property1, c => c.Condition((s, d) => string.IsNullOrWithSpace(d.Property1));
// Test Class
class TestClass {
public string Property1 {get; set;}
}
Now I want to write to a log, if the condition fails/the property is already set. Is there a way to achieve this or an alternative workaround?
I'm using AutoMapper v8.0.0
I have a simple solution but it is in a hacking-way style:
public static bool IsNullOrWithSpaceWithLog(string x){
log.Info("something")
return string.IsNullOrWithSpace(x);
}
CreateMap<TestClass, TestClass>()
.ForMember(d => d.Property1, c => c.Condition((s, d) => IsNullOrWithSpaceWithLog(d.Property1));
I have a destination class that combines properties from a source class and an inner class of that source class.
class Source {
public int Id {get;set;}
public int UseThisInt {get;set;}
public InnerType Inner {get;set;}
// other properties that the Destination class is not interested in
}
class InnerType {
public int Id {get;set;}
public int Height {get;set;}
// more inner properties
}
my destination class should combine UseThisInt and all properties of the InnerType.
class Destination {
public int Id {get;set;}
public int UseThisInt {get;set;}
public int Height {get;set;}
// more inner properties that should map to InnerType
}
Now my AutoMapper configuration looks like this:
CreatMap<Source, Destination>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Inner.Id))
.ForMember(d => d.Height, o => o.MapFrom(s => s.Inner.Height));
AutoMapper will correctly map UseThisInt between Source and Destination, but I would like to be able to let it map all the other properties in Destination like Height without an explicit ForMember configuration.
I tried using
Mapper.Initialize(cfg => cfg.CreateMap<Source, Destination>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Inner.Id))
.ForMember(d => d.UseThisInt, o => o.MapFrom(s => s.UseThisInt))
.ForAllOtherMembers(o => o.MapFrom(source=> source.Inner))
);
, but that did not achieve the intended result and left Destination.Height untouched.
Most examples of AutoMapper demonstrate creating a new Destination object from some source object, but AutoMapper can also be used to update an existing object taking those properties from the source object that are mapped and leaving any remaining properties untouched.
Consequently it is possible to map from the source to the destination in multiple steps.
So if you create a mapping configuration from InnerType like so:-
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>();
cfg.CreateMap<InnerType, Destination>();
});
Then you can make use of this ability to overlay mappings by mapping into the destination object twice.
var dest = Mapper.Map<Destination>(src);
Mapper.Map(src.Inner, dest);
One downside to this approach is that you need to be mindful of this when using the Mapper to generate a Destination object. However, you have the option of declaring this second mapping step within your AutoMapper configuration as an AfterMap instruction.
Mapper.Initialize(cfg => {
cfg.CreateMap<Source, Destination>()
.AfterMap((src, dest) => Mapper.Map(src.Inner, dest));
cfg.CreateMap<InnerType, Destination>();
});
With this updated configuration you can perform the mapping with a single Map call:-
var dest = Mapper.Map<Destination>(src);
CreateMap<Source, Destination>()
.AfterMap((src, dest) =>
{
dest.Height = src.Inner.Height;
});
I'm integrating with a 3rd party API which is returning a complex data structure and in a part of it I have the following relationship.
public class Parent{
public List<SmartLink> SmartLink { get; set; }
}
The SmartLink object looks like below:
public class SmartLink {
public Address AddressInfo { get; set; }
}
I have tried to map it in several ways, one of them below, but I still get a null on the AddressInfo object.
cfg.CreateMap<Address, AddressInfo>();
cfg.CreateMap<Source, Parent>()
//This is not allowed since Automapper cannot map to 2nd level
.ForMember(d => d.SmartLink.AddressInfo, map => map.MapFrom(src => src.Smartlink.ToList().Select(addr => addr.Address)));
The line below works perfectly:
.ForMember(d => d.SmartLink, map => map.MapFrom(s => s.Smartlink.ToList()))
How can I map/flatten a 3rd level property with Automapper, any pointers?
I had overthought on it. I simply added the following mapping and it worked.
cfg.CreateMap<Address, AddressInfo>();
cfg.CreateMap<SmartlinkPart, SmartLink>(MemberList.Destination)
.ForMember(d => d.AddressInfo, map => map.MapFrom(s => s.Address));
The idea is that for the member AddressInfo, the first line above will provide it's mapping instruction.
I have the following classes:
public class Entity
{
public string Name { get; set; }
}
public class SomethingDto
{
public string NameChanged { get; set; }
public void Mapping(Entity something)
{
NameChanged = something.Name;
}
}
I want to use the Mapping Method of the DTO to create the map as the following way:
conf.CreateMap<Entity, SomethingDto>().ForMember(t => t.NameChanged, opt => opt.MapFrom(t => t.Name));
There is a way in AutoMapper to create the maps with custom methods, who works with his projection?
You don't want to do it like that, because that makes the DTO aware of the entity and that would throw out the separation you'd get.
Now in this case, the line ForMember(t => t.NameChanged, opt => opt.MapFrom(t => t.Name)) will work because Name and NameChanged are both of type string. Say you'd like to do something along the lines of mapping identifier of type string with value '20180120-00123456' to two properties on the destination: a DateTime property and a ProductId property. You can do this two ways.
Simple
You would write two mapping functions in the class where you make the mapping and do it along the lines of:
ForMember(t => t.Date, opt => opt.MapFrom(t => RetrieveDate(t.Identifier)))
ForMember(t => t.ProductId, opt => opt.MapFrom(t => RetrieveProductId(t.Identifier)))
Complex
You would make a custom class OrderIdentifier (now I'm assuming the identifier is for an order) with only the Id property as string. Then you'd make two custom type converters, like the article describes.
I have been flattening domain objects into DTOs as shown in the example below:
public class Root
{
public string AParentProperty { get; set; }
public Nested TheNestedClass { get; set; }
}
public class Nested
{
public string ANestedProperty { get; set; }
}
public class Flattened
{
public string AParentProperty { get; set; }
public string ANestedProperty { get; set; }
}
// I put the equivalent of the following in a profile, configured at application start
// as suggested by others:
Mapper.CreateMap<Root, Flattened>()
.ForMember
(
dest => dest.ANestedProperty
, opt => opt.MapFrom(src => src.TheNestedClass.ANestedProperty)
);
// This is in my controller:
Flattened myFlattened = Mapper.Map<Root, Flattened>(myRoot);
I have looked at a number of examples, and so far this seems to be the way to flatten a nested hierarchy. If the child object has a number of properties, however, this approach doesn't save much coding.
I found this example:
http://consultingblogs.emc.com/owainwragg/archive/2010/12/22/automapper-mapping-from-multiple-objects.aspx
but it requires instances of the mapped objects, required by the Map() function, which won't work with a profile as I understand it.
I am new to AutoMapper, so I would like to know if there is a better way to do this.
I much prefer avoiding the older Static methods and do it like this.
Place our mapping definitions into a Profile. We map the Root first, and then apply the mappings of the Nested afterwards. Note the use of the Context.
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Root, Flattened>()
.AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));
CreateMap<Nested, Flattened>();
}
}
The advantage of defining both the mapping from Root to Flattened and Nested to Flatterned is that you retain full control over the mapping of the properties, such as if the destination property name is different or you want to apply a transformation etc.
An XUnit test:
[Fact]
public void Mapping_root_to_flattened_should_include_nested_properties()
{
// ARRANGE
var myRoot = new Root
{
AParentProperty = "my AParentProperty",
TheNestedClass = new Nested
{
ANestedProperty = "my ANestedProperty"
}
};
// Manually create the mapper using the Profile
var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();
// ACT
var myFlattened = mapper.Map<Root, Flattened>(myRoot);
// ASSERT
Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);
Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty);
}
By adding AutoMapper's serviceCollection.AddAutoMapper() from the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package to your start up, the Profile will be picked up automatically, and you can simply inject IMapper into wherever you are applying the mapping.
In the latest version of AutoMapper, there's a naming convention you can use to avoid multiple .ForMember statements.
In your example, if you update your Flattened class to be:
public class Flattened
{
public string AParentProperty { get; set; }
public string TheNestedClassANestedProperty { get; set; }
}
You can avoid the use of the ForMember statement:
Mapper.CreateMap<Root, Flattened>();
Automapper will (by convention) map Root.TheNestedClass.ANestedProperty to Flattened.TheNestedClassANestedProperty in this case. It looks less ugly when you're using real class names, honest!
2 more possible solutions:
Mapper.CreateMap<Nested, Flattened>()
.ForMember(s=>s.AParentProperty, o=>o.Ignore());
Mapper.CreateMap<Root, Flattened>()
.ForMember(d => d.ANestedProperty, o => o.MapFrom(s => s.TheNestedClass));
An alternative approach would be the below, but it would not pass the Mapper.AssertConfigurationIsValid().
Mapper.CreateMap<Nested, Flattened>()
//.ForMember map your properties here
Mapper.CreateMap<Root, Flattened>()
//.ForMember... map you properties here
.AfterMap((s, d) => Mapper.Map(s.TheNestedClass, d));
Not sure if this adds value to the previous solutions, but you could do it as a two-step mapping. Be careful to map in correct order if there are naming conflicts between the parent and child (last wins).
Mapper.CreateMap<Root, Flattened>();
Mapper.CreateMap<Nested, Flattened>();
var flattened = new Flattened();
Mapper.Map(root, flattened);
Mapper.Map(root.TheNestedClass, flattened);
To improve upon another answer, specify MemberList.Source for both mappings and set the nested property to be ignored. Validation then passes OK.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SrcNested, DestFlat>(MemberList.Source);
cfg.CreateMap<SrcRoot, DestFlat>(MemberList.Source)
.ForSourceMember(s => s.Nested, x => x.Ignore())
.AfterMap((s, d) => Mapper.Map(s.Nested, d));
});
Mapper.AssertConfigurationIsValid();
var dest = Mapper.Map<SrcRoot, DestFlat>(src);
I wrote extension method to solve similar problem:
public static IMappingExpression<TSource, TDestination> FlattenNested<TSource, TNestedSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression,
Expression<Func<TSource, TNestedSource>> nestedSelector,
IMappingExpression<TNestedSource, TDestination> nestedMappingExpression)
{
var dstProperties = typeof(TDestination).GetProperties().Select(p => p.Name);
var flattenedMappings = nestedMappingExpression.TypeMap.GetPropertyMaps()
.Where(pm => pm.IsMapped() && !pm.IsIgnored())
.ToDictionary(pm => pm.DestinationProperty.Name,
pm => Expression.Lambda(
Expression.MakeMemberAccess(nestedSelector.Body, pm.SourceMember),
nestedSelector.Parameters[0]));
foreach (var property in dstProperties)
{
if (!flattenedMappings.ContainsKey(property))
continue;
expression.ForMember(property, opt => opt.MapFrom((dynamic)flattenedMappings[property]));
}
return expression;
}
So in your case it can be used like this:
var nestedMap = Mapper.CreateMap<Nested, Flattened>()
.IgnoreAllNonExisting();
Mapper.CreateMap<Root, Flattened>()
.FlattenNested(s => s.TheNestedClass, nestedMap);
IgnoreAllNonExisting() is from here.
Though it's not universal solution it should be enough for simple cases.
So,
You don't need to follow flattening convention in destination's properties
Mapper.AssertConfigurationIsValid() will pass
You can use this method in non-static API (a Profile) as well
I created a simple example using AutoMappers new naming convention rules to map from flattened to nested objects, hope this helps
https://dotnetfiddle.net/i55UFK