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))
Related
I'm trying to read data from our database using entity framework and as the project already uses Automapper to convert from entities to Dtos it would make sense use Automappers Queryable Extensions to make life a bit easier. I'm using Microsoft.EntityFrameworkCore version 3.1.9.0
The problem is the returned array of BundleMetaDataDt.child is always null.
The query below returns plenty of data, but every BundleMetaDataDtos child value is null.
I have tried:
.Include(b => b.ChildBundle) before Where statement
.ForMember(dest => dest.ChildBundle, opt => opt.MapFrom(src => src.ChildBundleId))
.MaxDepth(2)
Classes: (There is more fields than shown below)
public partial class Bundle
{
public Guid? ChildBundleId { get; set; }
public Bundle ChildBundle { get; set; }
}
public class BundleMetaDataDto
{
[DataMember(IsRequired = true, Order = 15)]
public BundleMetaDataDto ChildBundle { get; set; }
}
Map:
cfg.CreateMap<Bundle, BundleMetaDataDto>()
.ForMember(dest => dest.ChildBundle, opt => opt.MapFrom(src => src.ChildBundle))
Query:
var bundles = context.Bundles
.Where(bundle => bundle.ChildBundle != null)
.ProjectTo<BundleMetaDataDto>(EntityConverter.MapperConfiguration)
.ToArray();
Thanks to #LucianBargaoanu I got it working by adding:
cfg.Advanced.RecursiveQueriesMaxDepth = 1;
I need a little help with some mappings I am doing.
I am mapping a Model which has two fields
public ProductCategory
public string FirstType
public string SecondType
to another Model which has only one field
public string ProductType
Now I have to map the First or Second Type to ProductType based on a the content of ProductCategory.And if the condition is not met the ProductType should be null
For example I need something like this:
.ForMember(dest => dest.ProductType, opt => opt.MapFrom(src =>
{
if (src.ProductCategory.Equals("something")
{
src.FirstType
}
else if (src.ProductCategory.Equals("something")
{
src.SecondType
}
else
{
null
}
}))
Of course the syntax is completely wrong and obviously won`t work , I just wanted to explain what I am trying to achieve.
I have a temporary solution
.ForMember(dest => dest.ProductType, opt => opt.MapFrom(src => src.ProductCategory.Contains("something") ? src.FirstType: src.SecondType))
but it is not completely what I need.
Any suggestions?
Thanks in advance
What you can do to avoid making the map code look very tangled is to actually separate it into methods that you actually know require some checking for the right value to be assigned.
Here's the code
.ForMember(dest => dest.ProductType, opt => opt.MapFrom(src => CalculateProductCategory(src.ProductCategory))) and then you write your own CalculateProductCategory
And your method would look something like this
public ProductType CalculateProductCategory(ProductCategory category) {
if (productCategory.Equals("something")
{
return FirstType
}
else if (productCategory.Equals("something")
{
return SecondType
}
else
{
return null
}
}
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();
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.