Ligne 144 :
Ligne 144 :
* [http://docs.automapper.org/en/stable/Flattening.html Flattening]
* [http://docs.automapper.org/en/stable/Flattening.html Flattening]
{{info | If the mapping to a destination property has not been configured, you will get an {{boxx|AutoMapper.AutoMapperMappingException}}}}
{{info | If the mapping to a destination property has not been configured, you will get an {{boxx|AutoMapper.AutoMapperMappingException}}}}
== Record ==
For records with a primary constructor, use {{boxx|ForCtorParam}} and fill all the constructor parameters.
<kode lang='cs'>
CreateMap<A, B>()
.ForCtorParam(nameof(B.P1), opt => opt.MapFrom(src => src.P1))
.ForCtorParam(nameof(B.Q2), opt => opt.MapFrom(src => src.P2))
.ForCtorParam(nameof(B.C1P1), opt => opt.MapFrom(src => src.C1.P1));
});
record A(int P1, int P2, C C1);
record B(int P1, int Q2, int C1P1);
record C(int P1);
</kode>
= [https://docs.automapper.org/en/stable/Flattening.html Flattening] =
= [https://docs.automapper.org/en/stable/Flattening.html Flattening] =
Dernière version du 4 mars 2025 à 15:17
Description
Permet le mapping d'un objet de type A vers un objet de type B.
AutoMapper utilise des convention de mapping afin de réduire le paramétrage:
Propriétés de même nom
Flattening
Ignore les références null
Liens
var config = new MapperConfiguration (cfg => {
cfg.CreateMap <Source , Dest >();
cfg.AddProfile <AppProfile >();
cfg.AddMaps (typeof (ContainerConfig ).Assembly );
});
IMapper mapper = config.CreateMapper ();
IMapper mapper = new Mapper (config);
AProfile.cs
internal sealed class AProfile : Profile
{
public AProfile ()
{
CreateMap <A , B >()
.ForMember (dest => dest .B1 , opt => opt .MapFrom (src => src .A1 ));
CreateMap <B , A >()
.ForMember (dest => dest .A1 , opt => opt .MapFrom (src => src .B1 ));
.ForAllOtherMembers (opt => opt .Ignore ());
CreateMap <A , C []>()
.ConvertUsing <AToCsConverter >();
}
}
public class AToCsConverter : ITypeConverter <A , C []>
{
public C [] Convert (A source , C [] destination , ResolutionContext context )
{
return new C [];
}
}
Program.cs
appBuilder.Services.AddAutoMapper (typeof (Program ));
Controllers/ItemController.cs
private readonly IMapper mapper ;
public ItemController (IMapper mapper )
{
this .mapper = mapper;
}
AutoMapper 12-
Add the nuget package AutoMapper.Extensions.Microsoft.DependencyInjection
Startup.cs
public void ConfigureServices (IServiceCollection services )
{
services.AddAutoMapper (typeof (Startup ));
services.AddAutoMapper (typeof (ProfileTypeFromAssembly1 ), typeof (ProfileTypeFromAssembly2 ));
Controllers/ItemController.cs
private readonly IMapper mapper ;
public ItemController (ItemDbContext context , IMapper mapper )
{
this .mapper = mapper;
}
WPF
App.xaml.cs
protected override void RegisterTypes (IContainerRegistry containerRegistry )
{
var config = new MapperConfiguration (cfg =>
{
cfg.AddMaps (typeof (App ).Assembly );
});
containerRegistry.RegisterSingleton <IMapper >(() => config.CreateMapper ());
}
Exemple
var config = new MapperConfiguration (cfg =>
{
cfg.CreateMap <A , B >()
.ForMember (dst => dst .Q2 , opts => opts .MapFrom (src => src .P2 ));
.ReverseMap ();
});
IMapper mapper = config.CreateMapper ();
C c = new C () { P1 = 3 };
A a = new A () { P1 = 1 , C1 = c };
B b = mapper.Map <B >(a );
class A
{
public int P1 { get ; set ; }
public int P2 { get ; set ; }
public C C1 { get ; set ; }
}
class B
{
public int P1 { get ; set ; }
public int Q2 { get ; set ; }
public int C1P1 { get ; set ; }
}
class C
{
public int P1 { get ; set ; }
}
If the mapping to a destination property has not been configured, you will get an AutoMapper.AutoMapperMappingException
Record
For records with a primary constructor, use ForCtorParam and fill all the constructor parameters.
CreateMap <A , B >()
.ForCtorParam (nameof (B .P1 ), opt => opt.MapFrom (src => src .P1 ))
.ForCtorParam (nameof (B .Q2 ), opt => opt.MapFrom (src => src .P2 ))
.ForCtorParam (nameof (B .C1P1 ), opt => opt.MapFrom (src => src .C1 .P1 ));
});
record A (int P1 , int P2 , C C1 );
record B (int P1 , int Q2 , int C1P1 );
record C (int P1 );
var config = new MapperConfiguration (cfg =>
{
cfg.CreateMap <SpecificItem , SpecificItemDto >()
.ForMember (dest => dest .Code , opt => opt .MapFrom (src => src .Item .Code ));
cfg.CreateMap <SpecificItem , SpecificItemDtoV2 >()
.IncludeMembers (x => x .Item );
cfg.CreateMap <Item , ItemDtoV2 >();
cfg.CreateMap <Item , SpecificItemDtoV2 >(MemberList .None )
.IncludeBase <Item , ItemDtoV2 >();
});
config.AssertConfigurationIsValid ();
IMapper mapper = config.CreateMapper ();
var specificItem = new SpecificItem
{
Item = new Item
{
Id = 1 ,
Code = 2
},
Category = 3
};
var specificItemDto = mapper.Map <SpecificItemDto >(specificItem );
var specificItemDtoV2 = mapper.Map <SpecificItemDtoV2 >(specificItem );
class Item
{
public int Id { get ; set ; }
public int Code { get ; set ; }
}
class SpecificItem
{
public int Category { get ; set ; }
public Item Item { get ; set ; }
}
class SpecificItemDto
{
public int ItemId { get ; set ; }
public int Code { get ; set ; }
public int Category { get ; set ; }
}
class ItemDtoV2
{
public int Id { get ; set ; }
public int Code { get ; set ; }
}
class SpecificItemDtoV2 : ItemDtoV2
{
public int Category { get ; set ; }
}
Unflattening
this .CreateMap <UserDto , User >()
.ForPath (dest => dest .Group .Id , opt => opt .MapFrom (src => src .GroupId ));
List and polymorphism
List <B > listB = Mapper.Map <List <A >, List <B >>(listA );
cfg.CreateMap <Parent1 , Parent2 >()
.Include <Child1 , Child2 >();
cfg.CreateMap <Child1 , Child2 >();
var lp1 = new List <Parent1 > { new Parent1 { P1 = 1 }, new Child1 { P1 = 2 , P2 = 22 } };
var lp2 = mapper.Map <List <Parent2 >>(lp1 );
class Parent1
{
public int P1 { get ; set ; }
}
class Child1 : Parent1
{
public int P2 { get ; set ; }
}
class Parent2
{
public int P1 { get ; set ; }
}
class Child2 : Parent2
{
public int P2 { get ; set ; }
}
var config = new MapperConfiguration (cfg =>
{
cfg.CreateMap <A , B >()
.ForMember (dest => dest .P1 ,
opt => opt .MapFrom ((src , _ , _ , context ) => src.P1 + (int )context.Items ["+"]));
});
IMapper mapper = config.CreateMapper ();
var a = new A { P1 = 1 };
var b = mapper.Map <B >(a , opt => opt .Items ["+" ] = 9 );
Map 1 object to multiple objects
class Group
{
public int Id { get ; set ; }
public ICollection <int > UserIds { get ; } = new List <int >();
}
class User
{
public int Id { get ; set ; }
public int GroupId { get ; set ; }
}
Flatten to ValueType
afficher var config = new MapperConfiguration (cfg =>
{
cfg.CreateMap <(Group group, int userId), User >()
.IncludeMembers (s => s .group ) // call the mapping Group → User
.ForMember (dest => dest .Id , opt => opt .MapFrom (src => src .userId ));
cfg.CreateMap <Group , User >()
.ForMember (dest => dest .GroupId , opt => opt .MapFrom (src => src .Id ))
.ForMember (dest => dest .Id , opt => opt .Ignore ());
});
IMapper mapper = config.CreateMapper ();
var group = new Group
{
Id = 9 ,
UserIds = {1 , 2 , 3 }
};
IEnumerable <(int groupId, int userId)> groupAndUserList = group .UserIds.Select (x => ValueTuple .Create (group .Id , x ));
var users = mapper.Map <IEnumerable <User >>(groupAndUserList ).ToList ();
Flatten with a ITypeConverter
afficher var config = new MapperConfiguration (cfg =>
{
cfg.CreateMap <int , User >()
.ForMember (dest => dest .Id , opt => opt .MapFrom (src => src ));
cfg.CreateMap <Group , User >()
.ForMember (dest => dest .GroupId , opt => opt .MapFrom (src => src .Id ))
.ForMember (dest => dest .Id , opt => opt .Ignore ());
cfg.CreateMap <Group , IEnumerable <User >>()
.ConvertUsing <GroupToUsersConverter >();
});
IMapper mapper = config.CreateMapper ();
var group = new Group
{
Id = 9 ,
UserIds = { 1 , 2 , 3 }
};
var users = mapper.Map <IEnumerable <User >>(group ).ToList ();
class GroupToUsersConverter : ITypeConverter <Group , IEnumerable <User >>
{
public IEnumerable <User > Convert (
Group source ,
IEnumerable <User > destination ,
ResolutionContext context )
{
foreach (var userId in source.UserIds)
{
var user = context.Mapper.Map <User >(userId );
context.Mapper.Map (source , user );
yield return user;
}
}
}
cfg.CreateMap <A , B >()
// map A .P2 → B .Q2 if A .P2 > 0
// if A .P2 <= 0 → B .Q2 = 0
.ForMember (dest => dest.Q2 ,
opt =>
{
opt.Condition (src => src .P2 > 0 );
opt.MapFrom (src => src .P2 );
});
cfg.CreateMap <A , B >()
.BeforeMap ((src , dest ) => src.P1 += 1)
.AfterMap ((src , dest ) => dest.P1 -= 1);
Inject a dependency during the mapping with a ValueResolver
Program.cs
appBuilder.Services.AddScoped <IUserProvider , UserProvider >()
.AddAutoMapper (typeof (AProfile ));
AProfile.cs
public class AProfile : Profile
{
public AProfile ()
{
this .CreateMap <A , B >()
.ForMember (x => x .User , opt => opt .MapFrom <UserValueResolver >());
}
}
public class UserValueResolver (IUserProvider userProvider) : IValueResolver <A , B , User >
{
public User Resolve (A source , B destination , User member , ResolutionContext context )
{
return userProvider.GetUser ();
}
}
Errors
this .CreateMap <Class1 , Class2 >()
.ForMember (dest => dest .Prop1 , opt => opt .MapFrom (src =>
{
// ...
}));
this .CreateMap <Class1 , Class2 >()
.ForMember (dest => dest .Prop1 , opt => opt .MapFrom ((src , dest ) =>
{
// ...
}));
var mapperMock = new Mock <IMapper >();
mapperMock.Setup (x => x .Map <ItemDto >(It .IsAny <Item >()))
.Returns (new ItemDto ());
MyProfileTests.cs
public class MyProfileTests
{
private readonly Fixture fixture ;
private readonly MapperConfiguration configuration ;
private readonly IMapper mapper ;
public MyProfileTests ()
{
fixture = new ();
configuration = new MapperConfiguration (cfg => cfg.AddProfile <MyProfile >());
mapper = configuration.CreateMapper ();
}
[Fact ]
public void MyProfile_IsValid ()
=> configuration.AssertConfigurationIsValid ();
[Fact ]
public void Map_SourceToDestination_ShouldMapCorrectly ()
{
var source = fixture.Create <MySource >();
var destination = mapper.Map <MyDestination >(source );
Assert.NotNull (destination );
Assert.Equal (source .Property1 , destination .Property1 );
}
Dependency Injection
The ctor of MyValueResolver take a parameter of type IMyService .
So to test the profile, you have to inject IMyService to resolve MyValueResolver .
MyProfileTests.cs
public class MyProfileTests
{
private readonly Fixture fixture ;
private readonly IMapper mapper ;
public MyProfileTests ()
{
mockMyService = new Mock <IMyService >();
mockApplicationUserProvider
.Setup (x => x .GetData ())
.Returns (fixture .Create <Data >());
var serviceProvider = new ServiceCollection ()
.AddSingleton (mockMyService.Object )
.AddSingleton <MyValueResolver >()
.AddAutoMapper (cfg => cfg.AddProfile <MyProfile >())
.BuildServiceProvider ();
mapper = serviceProvider.GetRequiredService <IMapper >();
}
Mock the mapper
var mockMapper = new Mock <IMapper >();
this .mockMapper.Setup (x => x .Map <MyDto >(It .IsAny <MyEntity >()))
.Returns (myDto );
var mapper = new Mapper (
new MapperConfiguration (
cfg =>
{
cfg.CreateMap <MyEntity , MyDto >()
.ForMember (dest => dest .Property1 , opt => opt .Ignore ());
}));
Installation
NuGet → AutoMapper
ASP.NET Core
NuGet → AutoMapper.Extensions.Microsoft.DependencyInjection
Startup.cs
public void ConfigureServices (IServiceCollection services )
{
services.AddAutoMapper (typeof (Startup ));
MyController.cs
private readonly IMapper mapper ;
public MyController (IMapper mapper )
{
this .mapper = mapper;
B b = this .mapper.Map <A , B >(a );
Configuration dans Profile