I am trying to solve seemingly trivial task. How to map string to a property of a collection? Bellow I show my current situation. I am trying to map the DeliveryType.UrlTemplate property of DeliveryNoteTableDto class to each PackageDto.UrlTemplate property. I am using AutoMapper. Currently I am getting error:
Custom configuration for members is only supported for top-level individual members on a type.
when performing the mapping. Any help?
classes
public class DeliveryNoteTableDto
{
public List<PackageDto> Packages { get; set; } = new List<PackageDto>();
public DeliveryTypeDto DeliveryType { get; set; }
}
public class PackageDto
{
public virtual string DeliveryKey { get; set; }
public virtual string UrlTemplate { get; set; }
}
public class DeliveryTypeDto
{
public virtual string Transporter { get; set; }
public virtual string UrlTemplate { get; set; }
}
mapping
map.ForMember(x => x.Packages.Select(p => p.UrlTemplate), opt => opt.MapFrom(x => x.DeliveryType.UrlTemplate));
Related
I'm implementing an WebAPI using dotnet core (2) and EF Core. The application uses UnitOfWork/Repository-pattern. I've come to a point where I need to implement a "Many-To-Many"-relation but I'm having some trouble. This is what I've got sofar:
Entities:
public class Team : DataEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TeamId { get; set; }
public int OrganizationID { get; set; }
public string Name { get; set; }
public string URL { get; set; }
public virtual ICollection<Season> Seasons { get; set; }
public ICollection<TeamMember> TeamMember { get; set; }
}
public class Member : DataEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MemberId { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MobilePhone { get; set; }
public string Email { get; set; }
public int RelatedTo { get; set; }
public int TeamRole { get; set; }
public ICollection<TeamMember> TeamMember { get; set; }
}
public class TeamMember
{
public int TeamId { get; set; }
public Team Team { get; set; }
public int MemberId { get; set; }
public Member Member { get; set; }
}
In my DbContext-class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TeamMember>()
.HasKey(t => new { t.TeamId, t.MemberId });
modelBuilder.Entity<TeamMember>()
.HasOne(tm => tm.Team)
.WithMany(t => t.TeamMember)
.HasForeignKey(tm => tm.TeamId);
modelBuilder.Entity<TeamMember>()
.HasOne(tm => tm.Member)
.WithMany(t => t.TeamMember)
.HasForeignKey(tm => tm.MemberId);
}
On my repository I have en extension for IncludeAll:
public static IQueryable<Team> IncludeAll(this IGenericRepository<Team> repository)
{
return repository
.AsQueryable()
.Include(s => s.Seasons)
.Include(tm => tm.TeamMember);
}
Everything builds as expected but when trying to invoke the controller-action that's fetching a Team (which I expect to include all members) or a Member (which I expect to include all the members teams - code not included above). SwaggerUI returns: TypeError: Failed to fetch. If I try to invoke the controller-action directly in chrome (http://localhost/api/Team/1) i get an incomplete result, but never the less...a result :) :
{
"value":
{
"teamId":1,
"organizationID":1,
"name":"Team1",
"url":"http://www.team1.com",
"seasons":[
{
"id":1,
"teamID":1,
"name":"PK4",
"description":null,
"startDate":"2017-09-01T00:00:00",
"endDate":"2018-12-31T00:00:00",
"created":"2017-12-01T00:00:00",
"updated":"2017-12-27T00:00:00",
"createdBy":"magnus",
"updatedBy":"magnus"
}],
"teamMember":[
{
"teamId":1
Am I missing something obvious?
Might be related to some circular reference issue?!
your teamMember property has an Team property which again has a teamMember properties and so on... When trying to serialize, a never ending loop would be created.
Maybe you can set the teamMember.Team property to null. I think you would also have to set the teamMember.Member.teamMember property to null.
You have a reference loop in your data structure (team -> team member -> team), and the JSON encoder can't deal with this.
I can think of two ways to solve this:
Configure the JSON encoder to ignore reference loops
In Startup.cs, add this:
services
.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
From the documentation:
ReferenceLoopHandling.Ignore: Json.NET will ignore objects in reference loops and not serialize them. The first time an object is encountered it will be serialized as usual but if the object is encountered as a child object of itself the serializer will skip serializing it.
Add the [JsonIgnore] attribute to the property you want to ignore
For example:
public class TeamMember
{
public int TeamId { get; set; }
[JsonIgnore]
public Team Team { get; set; }
}
I want to map source class to derived (from abstract) destination classes depend on value of some property.
I have the following source classes:
public partial class ApplicationDriver
{
public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }
}
public partial class ApplicationDriverEquipment
{
public int Id { get; set; }
[StringLength(256)]
public string Make { get; set; }
[StringLength(256)]
public string Model { get; set; }
[StringLength(256)]
public string Year { get; set; }
[StringLength(256)]
public string VINNumber { get; set; }
[StringLength(256)]
public string PlateNumber { get; set; }
[StringLength(256)]
public string CurrentMileage { get; set; }
[StringLength(256)]
public string Length { get; set; }
public string Type { get; set; }
public int DriverId { get; set; }
public virtual ApplicationDriver Driver { get; set; }
}
I want to map to the following classes, depend on Type parameter:
public class ApplicationDriverDomain
{
public List<ApplicationDriverEquipmentAbstractDomain> Equipments { get; set; }
}
public abstract class ApplicationDriverEquipmentAbstractDomain
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string PlateNumber { get; set; }
public string CurrentMileage { get; set; }
public string Type { get; protected set; }
}
public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTractorDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
}
public string VINNumber { get; set; }
}
public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTrailerDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
}
public string Length { get; set; }
}
public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentStraightTruckDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentCargoVanDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
I try to do it:
ApplicationDriverEquipmentAbstractDomain GetEquipment(Infrastructure.Asset.ApplicationDriverEquipment infrastructure)
{
ApplicationDriverEquipmentAbstractDomain result = null;
var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperApplicationModel>());
var mapper = config.CreateMapper();
switch (infrastructure.Type)
{
case ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor:
result = mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer:
result = mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck:
result = mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan:
result = mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
break;
}
return result;
}
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentAbstractDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>()
.ForMember(dest => dest.Type, opt => opt.ResolveUsing(GetEquipment))
;
CreateMap<Infrastructure.Asset.ApplicationDriver, ApplicationDriverDomain>()
.ForMember(dest => dest.Equipments, opt => opt.MapFrom(src => src.Equipments));
but I got an error:
"Error mapping types.\r\n\r\nMapping types:\r\nApplicationDriver ->
ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver ->
Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nType Map
configuration:\r\nApplicationDriver ->
ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver ->
Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nProperty:\r\nEquipments"
Updated:
So I believe I understand what you are trying to do, and apologies I may have slightly led you down the incorrect route. You flow is basically to distinguish what infrastructure type the source object is and then create that type of object. Also you need to understand the two different Mapper set up ways.
In the first part of your code you are trying to set it up with an instance of the Mapper but then using my Static style of using the Mapper.Map I would recommend always using the static style so that you have the ability to do some more dynamic ways of pulling mapping profiles in.
Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var domain = Mapper.Map<Domain.ApplicationDriverEquipmentTractorDomain>(inf);
Next you only need to reference that a mapping type from the underlying source to the domain types in your profile i.e.
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTractorDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentCargoVanDomain>();
Then what you need to do is to call your GetEquipment method from the mapping that describes the ApplicationDriver i.e.
CreateMap<ApplicationDriver, ApplicationDriverDomain>()
.ForMember(dest => dest.Equipments, opt => opt.ResolveUsing(x => x.Equipments.Select(GetEquipment)));
private ApplicationDriverEquipmentAbstractDomain GetEquipment(ApplicationDriverEquipmentInfrastructure infrastructure)
{
switch (infrastructure.Type)
{
case "Tractor":
return Mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
case "Trailer":
return Mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
case "StraightTruck":
return Mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
case "CargoVan":
return Mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
}
return null;
}
Example Usage:
Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var inf = new ApplicationDriverEquipmentInfrastructure()
{
CurrentMileage = "mil",
Length = "123",
Make = "ccc",
Model = "15",
Type = "Tractor",
VINNumber = "vin"
};
var driver = new ApplicationDriver()
{
Equipments = new List<ApplicationDriverEquipmentInfrastructure>() {inf}
};
var domain = Mapper.Map<ApplicationDriverDomain>(driver);
Inheritance in AM works by checking the type of the source, not by using a discriminator. That's what you were supposed to understand from the docs. One way to solve your problem is to pass an existing destination to Map. Created by smth like the GetEquipment method you have there. ApplyBaseMapping is a hack, you use Include/IncludeBase to reuse configuration. Unfortunately you've also hit a bug already fixed in the MyGet build, so the real error was kind of hidden from you. The only way to debug this in your version is by checking the execution plan.
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.
I have two classes:
public class ClassA
{
public ClassA()
{
LanguageInfo = new List<ClassALanguage>();
}
public long ID { get; set; }
public string Value { get; set; }
public List<ClassALanguage> LanguageInfo { get; set; }
}
public class ClassALanguage
{
public long ID { get; set; }
public string Name { get; set; }
public string Language { get; set; }
}
And I have a destination class:
public class WSClassA
{
public long ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
So I thought I could configure the mapping like this:
Mapper.CreateMap<ClassA, WSClassA>()
.ForMember(
ws => ws.Name,
opt => opt.MapFrom(clsA => clsA.LanguageInfo.Find(lg => lg.Language == languageSelected).Name));
The problem is after the first time the mapping is executed, it is not able to change. Even if the CreateMap() is executed with another value for languageSelected, the binding works as the first time.
Is there any solution to accomplish that?
In your case you need some context when you perform your mapping - the selected language.
Instead of using MapFrom use the ResolveUsing method with a custom IValueResolver.
See - https://automapper.codeplex.com/wikipage?title=Custom%20Value%20Resolvers
When you make your .Map call use the overload which allows you to modify the IMappingOperationOptions. Set the language you want to compare against in the interfaces Items collection.
Finally in your custom IValueResolver's Resolve method you can access those items via ResolutionResult.Context.Options.Items property.