Context:
There 2 main classes that look something like this:
public class Source
{
public Dictionary<AttributeType, object> Attributes { get; set; }
}
public class Target
{
public string Title { get; set; }
public string Description { get; set; }
public List<Attribute> Attributes { get; set; }
}
And the sub / collection / Enum types:
public class Attribute
{
public string Name { get; set; }
public string Value { get; set; }
}
public enum AttributeType
{
Title,
Description,
SomethingElse,
Foobar
}
Currently my Map looks like this:
CreateMap<Source, Target>()
.ForMember(dest => dest.Description, opt => opt.MapAttribute(AttributeType.Description))
.ForMember(dest => dest.Title, opt => opt.MapAttribute(AttributeType.Title));
Where MapAttribute gets the item from the Dictionary and using the AttributeType I've provided, adds it to the target collection as a (name & value) object (using a try get and returning an empty if the key does not exist)...
After all of this my Target set end's up looking like this:
{
title: "Foo",
attributes: [
{ name: "SomethingElse", value: "Bar" },
{ name: "Title", value: "Foo"}
]
}
Question:
How do I go about mapping the rest of the items to the target class, but I need to be able to exclude specific keys (like, title or description). E.G. Source.Attribute items that have a defined place in target gets excluded from the Target.Attributes collection, and "left-over" properties still go to Target.Attributes.
For even more clarity (if my source looks like this):
{ attributes: { title: "Foo", somethingelse: "Bar" } }
it would map to a target like this:
{ title: "Foo", attributes: [{ name: "SomethingElse", value: "Bar" }] }
I've attempted this, but it does not compile, stating the following:
Custom configuration for members is only supported for top-level individual members on a type.
CreateMap<KeyValuePair<AttributeType, object>, Attribute>()
.ForSourceMember(x => x.Key == AttributeType.CompanyName, y => y.Ignore())
It is possible to prefilter values on the mapping configuration, that some values would even not go to the target
I'm using automapper v6.1.1, there are some difference, but the idea should be the same)
To make things works I have to add KeyValuePair<AttributeType, object> to Attribute mapping first (MapAttribute just returns dictionary value)
CreateMap<KeyValuePair<AttributeType, object>, Attribute>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(s => s.Key))
.ForMember(dest => dest.Value, opt => opt.MapFrom(s => s.Value));
As it's defined which attributes should be ignored, they will goes to ignore list, based on which mapping will filter out excess attributes
var ignoreAttributes = new[] {AttributeType.Description, AttributeType.Title};
CreateMap<Source, Target>()
.ForMember(dest => dest.Description, opt => opt.MapFrom(s => s.MapAttribute(AttributeType.Description)))
.ForMember(dest => dest.Title, opt => opt.MapFrom(s => s.MapAttribute(AttributeType.Title)))
.ForMember(dest=>dest.Attributes, opt=> opt.MapFrom(s=>s.Attributes
.Where(x=> !ignoreAttributes.Contains(x.Key))));
Based on the final mapping example
var result = Mapper.Map<Target>(new Source
{
Attributes = new Dictionary<AttributeType, object>
{
{AttributeType.Description, "Description"},
{AttributeType.Title, "Title"},
{AttributeType.SomethingElse, "Other"},
}
});
result will have filled Titel, Description and just one attribute SomethingElse
Related
I've got a source class like the following:
public class Source
{
public Field[] Fields { get; set; }
public Result[] Results { get; set; }
}
And have a destination class like:
public class Destination
{
public Value[] Values { get; set; }
}
So I want to map from EITHER Fields or Results to Values depending on which one is not null (only one will ever have a value).
I tried the following map:
CreateMap<Fields, Values>();
CreateMap<Results, Values>();
CreateMap<Source, Destination>()
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Fields != null);
opt.MapFrom(s => s.Fields });
})
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Results != null);
opt.MapFrom(s => s.Results);
});
Only issue with this is that it looks if the last .ForMember map doesn't meet the condition it wipes out the mapping result from the first map.
I also thought about doing it as a conditional operator:
opt => opt.MapFrom(s => s.Fields != null ? s.Fields : s.Results)
But obviously they are different types so don't compile.
How can I map to a single property from source properties of different types based on a condition?
Thanks
There is a ResolveUsing() method that allows you for more complex binding and you can use a IValueResolver or a Func. Something like this:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.ResolveUsing<ConditionalSourceValueResolver>());
And the value resolver depending on your needs may look like:
public class ConditionalSourceValueResolver : IValueResolver<Source, Destination, Value[]>
{
public Value[] Resolve(Source source, Destination destination, Value[] destMember, ResolutionContext context)
{
if (source.Fields == null)
return context.Mapper.Map<Value[]>(source.Results);
else
return context.Mapper.Map<Value[]>(source.Fields);
}
}
Following #animalito_maquina answer.
Here is an update for 8.0 Upgrade:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.MapFrom<ConditionalSourceValueResolver>());
And to save you time, ValueResolvers are not supported for Queryable Extensions
ResolveUsing is not available, try this.
It's working for me
CreateMap<Source, Destination>()
.ForMember(opt => opt.value, map =>
map.MapFrom((s, Ariel) => s.Fields != null ? s.Fields : s.Results));
I have two classes and map one to other with Automapper. For instance:
public class Source
{
// IdName is a simple class containing two fields: Id (int) and Name (string)
public IdName Type { get; set; }
public int TypeId {get; set; }
// another members
}
public class Destination
{
// IdNameDest is a simple class such as IdName
public IdNameDest Type { get; set; }
// another members
}
Then I use Automapper to map Source to Destination:
cfg.CreateMap<Source, Destination>();
It works properly but sometimes member Type in class Source becomes null. In these cases I would like to map member Type in class Destination from TypeId property. That's what I want in a nutshel:
if Source.Type != null
then map Destination.Type from it
else map it as
Destination.Type = new IdNameDest { Id = Source.Id }
Is it possible with AutoMapper?
You can use the .ForMember() method while declaring the mapping.
Like so :
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type != null ? src.Type : new IdNameDest { Id = src.Id }));
While LeeeonTMs answer works fine AutoMapper provides a specialised mechanism to substitute null values. It "allows you to supply an alternate value for a destination member if the source value is null anywhere along the member chain" (taken from the AutoMapper manual).
Example:
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Value, opt => opt.NullSubstitute(new IdNameDest { Id = src.Id }));
With C# 6.0, the null-coalescing operator can be used.
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type ?? new IdNameDest { Id = src.Id }));
I managed to resolve it with Mapping Resolvers
public class SomeResolver : IValueResolver<Soruce, Dest, Guid>
{
public Guid Resolve(Source source, Dest destination, Guid, destMember, ResolutionContext context)
{
destination.Value= source.Value!=null ? source.Value:0;
return destination.MainGuid = Guid.NewGuid();
}
}
and then on mapping configuraiton
CreateMap<BioTimeEmployeeSummaryDTO, BioTimeEmployeeAttendanceSummary>()
.ForMember(dest => dest.MainGuid, opt => opt.MapFrom<YourResolverClass>())
.ReverseMap();
AutoMapper 5.3.0 alpha (Yet to upgrade to the latest and greatest)
I have a DTO as follows:
public class AoACrudFieldValuesGdataHealthInput
{
public AoACrudFieldValuesGdataHealthInput()
{
PrevHealthAssessment = new HashSet<HealthPrevHealthAssessmentEnum>();
}
public HashSet<HealthPrevHealthAssessmentEnum> PrevHealthAssessment { get; set; }
}
and a EF POCO of:
[Table("Health", Schema = "gdata")]
public class GdataHealthTableModel : AuditedEntity
{
public GdataHealthTableModel()
{
//PrevHealthAssessment = new HashSet<HealthPrevHealthAssessmentEnum>();
}
[ForeignKey("Id")]
public ICollection<GdataHealthPrevAssesmentTableModel> PrevHealthAssessment { get; set; }
}
and:
[Table("HealthPrevAssesment", Schema = "gdata")]
public class GdataHealthPrevAssesmentTableModel : AuditedEntity
{
public HealthPrevHealthAssessmentEnum Assessment { get; set; }
}
I need help extending my map:
CreateMap<AoACrudFieldValuesGdataHealthInput, GdataHealthTableModel>();
such that AoACrudFieldValuesGdataHealthInput.PrevHealthAssessment ends up in GdataHealthTableModel.PrevHealthAssessment. Currently I get an error as my CreateMap() is not detailed enough - naturally.
Is this possible? Or do I get the mapper to ignore that field and do it by hand?
NOTE: For both EF POCO's I have omitted the Id field which is an auto increment for sake of brevity.
Define an additional map for HealthPrevHealthAssessmentEnum -> GdataHealthPrevAssesmentTableModel:
CreateMap<AoACrudFieldValuesGdataHealthInput, GdataHealthTableModel>()
.ForMember(dest => dest.PrevHealthAssessment, o => o.MapFrom(src => src.PrevHealthAssessment));
CreateMap<HealthPrevHealthAssessmentEnum, GdataHealthPrevAssesmentTableModel>()
.ForMember(dest => dest.Id, o => o.Ignore()) // auto ID
.ForMember(dest => dest.Assessment , o => o.MapFrom(src => src));
Not sure of AutoMapper can convert an ICollection to a HashSet out of the box, I think it can.
I am trying to use AutoMapper 3 to project a class with an Integer property to another class with a String property.
When the query is executed then I get the following exception:
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
Here are the relevant parts of the code:
public partial class Lookup
{
public int LookupId { get; set; }
public int LookupTypeId { get; set; }
public string Value { get; set; }
public int SequencialOrder { get; set; }
public virtual LookupType LookupType { get; set; }
}
public class LookupProfile : Profile
{
protected override void Configure()
{
CreateMap<Lookup, SelectListItem>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.LookupId.ToString()))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Value));
}
}
And the query looks like:
Provinces = _db.Lookups.Project().To<SelectListItem>().ToList()
Question:
Is there a way I could configure the LookupProfile to do the proper mapping and still work inside Linq To Entities?
Or is there another way I could make the projection work with Linq to Entities?
The solution was to use the SqlFunctions.StringConvert function.
Here is the modified profile code that made everything work:
public class LookupProfile : Profile
{
protected override void Configure()
{
CreateMap<Lookup, SelectListItem>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => SqlFunctions.StringConvert((double)src.LookupId)))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Value));
}
}
I'll leave this answer here in case anyone else stumbles upon the same issue I had.
One problem with the current accepted answer is that if you're on an ASP.NET MVC project using client-side validation through helpers, you'll get a validation error for the ID field (if it's a number): The field [field] must be a number. That happens because the result from SqlFunctions.StringConvert returns a string with several leading spaces, so the unobtrusive validator doesn't see it as a number.
The way I solved this issue on my own was to create a generic SelectListItem<T> class that inherits from SelectListItem, hides the original Value property and implements its own Value setter:
public class SelectListItem<T> : SelectListItem
{
public new T Value {
set {
base.Value = value.ToString();
}
// Kind of a hack that I had to add
// otherwise the code won't compile
get {
return default(T);
}
}
}
Then on the Automapper profile I would map the items like so:
public class LookupProfile : Profile
{
protected override void Configure()
{
//Use whatever datatype is appropriate: decimal, int, short, etc
CreateMap<Lookup, SelectListItem<int>>()
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.LookupId))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Value));
}
}
And finally on the Service layer, I would map the entities to the generic class and return an IEnumerable<SelectListItem>.
public IEnumerable<SelectListItem> GetList() {
return _db.Lookups.Project().To<SelectListItem<int>>().ToList();
}
This way you'll get the right value for the Value property without trailing spaces.
Is it possible to ignore mapping a member depending on the value of a source property?
For example if we have:
public class Car
{
public int Id { get; set; }
public string Code { get; set; }
}
public class CarViewModel
{
public int Id { get; set; }
public string Code { get; set; }
}
I'm looking for something like
Mapper.CreateMap<CarViewModel, Car>()
.ForMember(dest => dest.Code,
opt => opt.Ignore().If(source => source.Id == 0))
So far the only solution I have is too use two different view models and create different mappings for each one.
The Ignore() feature is strictly for members you never map, as these members are also skipped in configuration validation. I checked a couple of options, but it doesn't look like things like a custom value resolver will do the trick.
Use the Condition() feature to map the member when the condition is true:
Mapper.CreateMap<CarViewModel, Car>()
.ForMember(dest => dest.Code, opt => opt.Condition(source => source.Id != 0))
I ran into a similar issue, and while this will overwrite the existing value for dest.Code with null, it might be helpful as a starting point:
AutoMapper.Mapper.CreateMap().ForMember(dest => dest.Code,config => config.MapFrom(source => source.Id != 0 ? null : source.Code));
Here is the documentation of the conditional mapping:
http://docs.automapper.org/en/latest/Conditional-mapping.html
There's also another method called PreCondition very useful on certain scenarios since it runs before the source value is resolved in the mapping process:
Mapper.PreCondition<CarViewModel, Car>()
.ForMember(dest => dest.Code, opt => opt.Condition(source => source.Id == 0))