I'm working on an .NET 5 API.
I have to reply to a get call with a Json that serializes the UnitDto class and inside it the list of all the InstDto class but I need a property that resides on the UnitInst object (the table many to many)
My classes:
public class Unit
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<UnitInst> UnitInsts { get; set; }
}
public class Inst
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<UnitInst> UnitInsts { get; set; }
}
public class UnitInst
{
public long Id { get; set; }
public long UnitId { get; set; }
public virtual Unit Unit { get; set; }
public long InstId { get; set; }
public virtual Inst Inst { get; set; }
public string IPv4 { get; set; } // the property that is important
}
My dto's
public class UnitDto
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<InstDTO> Insts { get; set; }
}
public class InstDTO
{
public long Id { get; set; }
public string Name { get; set; }
public string IPv4 { get; set; } // I need serialize this property in my response json
}
I map in this way and it's ok but i can't retrive IPv4 property from UnitInst class (the many to many table)
CreateMap<Unit, UnitDto>()
.ForMember(dto => dto.Insts, opt => opt.MapFrom(x => x.UnitInsts.Select(y => y.Inst).ToList()))
.PreserveReferences();
How can I solve?
Normally you would create 2 maps (Unit -> UnitDto and Inst -> InstDto) and use the Select trick you've shown. But that is applicable only when the join entity has no additional data, which is not the case here.
So you need to map the join entity collection directly:
CreateMap<Unit, UnitDto>()
.ForMember(dst => dst.Insts, opt => opt.MapFrom(src => src.UnitInsts)); // <-- no Select
and create additional map UnitInst -> InstDto:
cfg.CreateMap<UnitInst, InstDTO>()
.IncludeMembers(src => src.Inst) // needs `Inst` -> `InstDTO` map
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Inst.Id));
Here AutoMapper IncludeMembers is used to map the Inst members specified by the regular Inst -> InstDTO map, and the target Id property is mapped explicitly because both source and "included" objects have a property with the same name, in which case the source have priority, but you want Id to be Inst.Id or InstId.
Finally the Inst -> InstDTO map:
CreateMap<Inst, InstDTO>()
.ForMember(dst => dst.IPv4, opt => opt.Ignore());
Related
I have tried using a custom converted
CreateMap<ChildB, string>().ConvertUsing(src => src.Name);
But I run into the scenario when I want to sometimes get the string like above or sometimes I want to just get the Guid:
CreateMap<ChildB, Guid>().ConvertUsing(src => src.Id);
It seems to throw and error as it always converts the Name. The Objects are like this:
public class ParentA
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<ChildB>? {get;set;}
...
}
public class ChildB
{
public Guid Id {get;set;}
public string Name {get;set;}
...
}
public class ParentADTO1
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<string> ChildNames{get;set;}
...
}
public class ParentADTO2
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<Guid> ChildIds {get;set;}
...
}
So the question is, can I use the CreateMap function like so:
CreateMap<ParentA,ParentADTO1>()
...
.ForMember(ent => ent.ChildNames, opt => opt.MapFrom(???))
CreateMap<ParentA,ParentADTO2>()
...
.ForMember(ent => ent.ChildIds, opt => opt.MapFrom(???))
Your help is greatly appreciated!!
Thanks
Jon
You can set up the mapping configuration like this (I suppose the property is named Children):
public class ParentA
{
public Guid Id {get;set;}
public string Name {get;set;}
public ICollection<ChildB> Children {get;set;}
// ...
}
CreateMap<ParentA,ParentADTO1>()
// ...
.ForMember(ent => ent.ChildNames, opt => opt.MapFrom(x => x.Children.Select(y => y.Name).ToArray()))
CreateMap<ParentA,ParentADTO2>()
// ...
.ForMember(ent => ent.ChildIds, opt => opt.MapFrom(x => x.Children.Select(y => y.Id).ToArray()))
#Markus answer is certainly valid and is an excellent solution. FWIW, here is another approach you could by using the built-in AutoMapper's Flattening pattern. Simply add a Get[PropertyName] method to the source class to combine the child objects in whatever way you want it. Here is the complete example:
public class Parent
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Child> Children { get; set; }
public ICollection<string> GetChildNames()
{
return Children.Select(x => x.Name).ToArray();
}
public ICollection<Guid> GetChildIds()
{
return Children.Select(x => x.Id).ToArray();
}
}
public class Child
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ParentWithChildNames
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<string> ChildNames { get; set; }
}
public class ParentWithChildIds
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Guid> ChildIds { get; set; }
}
//No need to map the member, you could use Get[PropertyName] approach to automatically map it
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Parent, ParentWithChildIds>();
cfg.CreateMap<Parent, ParentWithChildNames>();
});
var mapper = config.CreateMapper();
This is my class which holds database data:
public partial class PermissionGroup
{
public int Id { get; set; }
public string Name { get; set; }
// other database properties
public virtual ICollection<GroupActionPermission> GroupActionPermissions { get; set; }
}
And that's my dto's:
public class PermissionGroupDTO
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<GroupActionPermissionDTO> ActionPermissions { get; set; }
}
public class GroupActionPermissionDTO
{
public int Id { get; set; }
public int GroupId { get; set; }
public int PermissionActionId { get; set; }
public PermissionGroupDTO Group { get; set; }
}
Now, I am making mapping:
public IEnumerable<PermissionGroupDTO> GetGroups()
{
return OnConnect<IEnumerable<PermissionGroupDTO>>(db =>
{
return db.PermissionGroups
.Include(i => i.GroupActionPermissions)
.ProjectTo<PermissionGroupDTO>()
.ToList();
});
}
And I am getting collection of PermissionGroupDTO which should contains collection of GroupActionPermissionDTO, but that collection stays null. Is there something wrong with my code? I am afraid that automapper can map collections from foreign keys.
Also, thats my automapper initializer:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>();
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
I believe the reason is desribed here http://docs.automapper.org/en/stable/Queryable-Extensions.html
Note that for this feature to work, all type conversions must be explicitly handled in your Mapping.
So that means you should manually configure the mapping:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>()
.ForMember(dto => dto.ActionPermissions , conf => conf.MapFrom(ol => ol.GroupActionPermissions )));;
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
BTW, note that fields are named differently: GroupActionPermissions vs. ActionPermissions. This is also the reason why automapper doesn't map it automatically and then you should use the manual configuration I wrote.
I have a View Model such as
public class RootViewModel
{
public CreateCompanyViewModel Company { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public CreateUserTypeViewModel UserType { get; set; }
}
And CreateCompanyViewModel and CreateUserTypeViewModel are like
public class CreateCompanyViewModel
{
public string CompanyName { get; set; }
}
public class CreateUserTypeViewModel
{
public string UserTypeName { get; set; }
}
I want this RootVM to be flattened to multiple DTO's. The 3 DTO's for the above RootVM I have are like
public class UserDTO
{
public string Name { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
public class CompanyDTO
{
public string CompanyName { get; set; }
}
public class UserTypeDTO
{
public string UserTypeName { get; set; }
}
NOTE : Note that CompanyDTO and UserTypeDTO are not nested object (part of) UserDTO unlike RootVM.
When I'm doing the mapping using AutoMapper RootVM properties gets mapped to UserDTO but CompanyDTO and UserTypeDTO are null as expected.
I tried mapping them by using ForMember function with MapFrom and ResolveUsing methods, but both of them shows error as
Custom configuration for members is only supported for top-level
individual members on a type.
UPDATE
Below is my mapping code
CreateMap<RootViewModel, CompanyDTO>();
CreateMap<RootViewModel, UserDTO>();
CreateMap<RootViewModel, UserTypeDTO>();
CreateMap<CreateCompanyViewModel, CompanyDTO>();
CreateMap<CreateUserTypeViewModel, UserTypeDTO>();
I'm using AutoMapper 5.2.0
UPDATE - Fix :
Well what I found is, either I have to use .ForMember for all the properties manually, else for automatic convention to work, I need to use https://github.com/AutoMapper/AutoMapper/wiki/Flattening or https://arnabroychowdhurypersonal.wordpress.com/2014/03/08/flattening-object-with-automapper/.
This is the only way to make it work.
Wish I could do .ForMember(d => d, s => s.MapFrom(x => x.Company)) and it'd map all the properties from CreateCompanyViewModel => CompanyDTO. This would have been very handy, but AutoMapper doesn't supports this.
Try following
CreateMap<CreateCompanyViewModel, CompanyDTO>();
CreateMap<CreateUserTypeViewModel, UserTypeDTO>();
CreateMap<RootViewModel, CompanyDTO>()
.ForMember(dest => dest.CompanyName, opt => opt.MapFrom(src => src.Company.CompanyName));
CreateMap < RootViewModel, UserTypeDTO()
.ForMember(dest => dest.UserTypeName, opt => opt.MapFrom(src => src.UserType.UserTypeName));
CreateMap<RootViewModel, UserDTO>();
I have the following class
public class Unit
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("unittype")]
public UnitType UnitType { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("language")]
public string Language { get; set; }
[JsonProperty("managers")]
public List<Manager> Managers { get; set; }
[JsonProperty("logoURL")]
public string LogoURL { get; set; }
[JsonProperty("naceCodeList")
public List<string> NaceCodeList { get; set; }
[JsonProperty("tags")]
public List<string> Tags { get; set; }
[JsonIgnore]
public string JoinedTags => string.Join(",", Tags);
[JsonProperty("respondents")]
public List<Respondent> Respondents { get; set; }
[JsonProperty("childUnits")]
public List<Unit> ChildUnits { get; set; }
public string Parent { get; set; }
}
I also have the following in the constructor of my BaseRepository
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Unit, Unit>();
});
My problem is that when I, in my repository, do Mapper.Map(unitToUpdate, unit); it empties all the List<whatever> properties.
Is this default behavior? Am I doing something wrong here?
For completeness, here is my UpdateUnit function
public void UpdateUnit(ClaimsPrincipal user, string orgId, Unit unitToUpdate)
{
var org = Get(user, orgId);
var unit = org.GetUnit(unitToUpdate.Id);
Mapper.Map(unitToUpdate, unit);
Update(user, org);
}
The stored object is another type containing a hierarchy of Unit objects representing a company. The org object is what is stored in the database, and the class representing the org object has it's own functions to load Unit objects inside itself.
I guess that your problem is that you need first to define the mappings for all collections from both objects and after that you need to define the mappings for objects.
Idea is to get from the bottom to the top (from the nested one to the Unit).
For example (taking into consideration only Respondents:
Mapper.CreateMap<RespondentDb, RespondentDto>();
Mapper.CreateMap<UnitDb, UnitDto>()
.ForMember(dest => dest.Respondents, opt => opt.MapFrom(src => src.Respondents);
Some references:
https://github.com/AutoMapper/AutoMapper/wiki/Nested-mappings
https://codereview.stackexchange.com/questions/70856/automapper-mapping-a-nested-collection-to-a-list (here we have an object that only contains a list, maybe it's good to know that too as you can encounter the same scenario)
I've got a source model defined as
public class SourceRoot
{
...
public Organisation Organisation { get; set; }
...
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
and a destination defined as:
public class DestinationRoot
{
...
public Organisation Organisation { get; set; }
public ContributesTo ContributesTo { get; set; }
}
public class Organisation
{
public long? Id { get; set; }
public string Name { get; set; }
}
public class ContributesTo
{
public string Currency { get; set; }
public double SupplementaryAmount { get; set; }
public decimal BaseConversionRate { get; set; }
}
I want to map from the SourceRoot to the DestinationRoot add copy from the source Organisation to the destination Organisation AND ContributesTo.
I have the following configuration for AutoMapper:
public static class AutoMapperConfig
{
public static MapperConfiguration RegisterMappings()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<MyProfile>();
});
return config;
}
}
public class MyProfile : Profile
{
protected override void Configure()
{
this.CreateMap<SourceRoot, DestinationRoot>();
this.CreateMap<Source.Organisation, Destination.Organisation>();
this.CreateMap<Source.Organisation, Destination.ContributesTo>();
}
}
Using this current profile the Organisation gets mapped but the ContributesTo comes out as null.
Note that I'm using version 4.2 of AutoMapper where the static methods have been deprecated so trying to steer away from that. Normally I would do:
Mapper.CreateMap<SourceRoot, DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom( s=> Mapper.Map<ContributesTo>(s.Organisation)));
But this is not advised anymore (referencing the static methods). Is there an alternative way of doing this?
Thanks
Just add mapping for ContributesTo destination member:
protected override void Configure ()
{
CreateMap<Source.SourceRoot, Destination.DestinationRoot>()
.ForMember(d => d.ContributesTo, opt => opt.MapFrom(s => s.Organisation));
CreateMap<Source.Organisation, Destination.Organisation>();
CreateMap<Source.Organisation, Destination.ContributesTo>();
}
Otherwise Automapper finds that both source and destinaton roots have property Organisation and it maps only this property. Automapper cannot understand that it should use one property of source to map several properties of destination (which do not match by name). Note that you don't need to specify mapping for Organisation member, because it matches property name in destination object.