I have a unique requirement when mapping some elements using Automapper.
I am not finding any effective solution with built scenarios:
I want to add phone number details to the contacts list if the phone number is not null
I want to add email address details to the contacts list if the email is not null
CreateMap<UserModel, UserDefinition>()
.ForMember(d => d.Id, o => o.Ignore())
.ForMember(d => d.UserName, o => o.MapFrom(s => s.Username))
.ForMember(d => d.Contacts, o =>
new List<UserContactDefinition>()
{
o.MapFrom(s => !string.IsNullOrWhiteSpace(s.PhoneNumber) ?
new UserContactDefinition
{
Type = ContactType.Phone,
IsPrimary = true,
Label = s.PhoneType,
Value = s.PhoneNumber
}: null,
o.MapFrom(s => !string.IsNullOrWhiteSpace(s.ContactEmail) ?
new UserContactDefinition
{
Type = ContactType.Email,
IsPrimary = true,
Label = s.EmailType,
Value = s.Email
}: null
}
);
This code is not working and I don't want to add empty elements if there is no value.
Any leads to this?
For your scenario, you need the Custom Value Resolver to map the destination member for the Contacts property.
Implement UserContactDefinitionListResolver custom value resolver.
public class UserContactDefinitionListResolver : IValueResolver<UserModel, UserDefinition, List<UserContactDefinition>>
{
public List<UserContactDefinition> Resolve(UserModel src, UserDefinition dst, List<UserContactDefinition> dstMember, ResolutionContext ctx)
{
dstMember = new List<UserContactDefinition>();
if (!string.IsNullOrWhiteSpace(src.PhoneNumber))
dstMember.Add(new UserContactDefinition
{
Type = ContactType.Phone,
IsPrimary = true,
Label = src.PhoneType,
Value = src.PhoneNumber
});
if (!string.IsNullOrWhiteSpace(src.ContactEmail))
dstMember.Add(new UserContactDefinition
{
Type = ContactType.Email,
IsPrimary = true,
Label = src.EmailType,
Value = src.ContactEmail
});
return dstMember;
}
}
Add mapping configuration/profile for the member Contacts to use the UserContactDefinitionListResolver.
CreateMap<UserModel, UserDefinition>()
.ForMember(d => d.Id, o => o.Ignore())
.ForMember(d => d.UserName, o => o.MapFrom(s => s.Username))
.ForMember(d => d.Contacts, o => o.MapFrom(new UserContactDefinitionListResolver()));
Demo # .NET Fiddle
Related
Mapping:
.ForMember(dest => dest.DisplayName, opt =>
{
opt.PreCondition(location => location.Parent != null);
opt.MapFrom(src => src.Name + ", " + src.Parent.Name);
})
.ForMember(dest => dest.DisplayName, opt =>
{
opt.PreCondition((src, dest, context) => dest.DisplayName == null);
opt.MapFrom(src => src.Name);
})
Expected result:
If the first condition is met don't override the mapping.
What actually happens:
The second mapping is overriding the first mapping.
How can I solve this?
It doesn't work because you are overwriting previous mapping expressions calling another ForMember() for the same member, which is your case is DisplayName. Consider such case:
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "1"))
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "2"))
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "3"));
Which value will be mapped to DisplayName?
3
So in your case, your first conditional mapping expression is overwriten by the second one. To make it work, join the conditional mapping into one mapping expression:
.ForMember(
dest => dest.DisplayName,
opts => opts.MapFrom((src, dest) =>
{
if (src.Parent != null)
{
return string.Join(", ", src.Name, src.Parent.Name);
}
else
{
if (dest.DisplayName is null)
{
return src.Name;
}
else
{
return "Some other value when no conditions were met.";
}
}
}));
It would be a cool feature to have but I don't see it anywhere in Automapper documentation.
This should however work in your case if the logic is not more complex.
.ForMember(dest => dest.DisplayName, opt =>
{
opt.MapFrom(src => src.Name + (location.Parent != null ? ", " + src.Parent.Name : null));
})
I want to map most properties of the current object (this a FinancialBase instance) to another object (the 'destination' object, the schedule, an instance of a Schedule class). However, I need to keep a small set of the destination's properties.
I've got it working with a 'hack' where I capture the values explicitly then use these in the AfterMap function. See example code.
var id = schedule.Id;
var parentId = schedule.ParentId;
var scheduleNo = schedule.ScheduleNo;
var schName = schedule.SchName;
var config = new MapperConfiguration(
cfg => cfg.CreateMap<FinancialBase, Schedule>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.ParentId, opt => opt.Ignore())
.ForMember(d => d.ScheduleNo, opt => opt.Ignore())
.ForMember(d => d.SchName, opt => opt.Ignore())
.AfterMap((s, d) => d.Id = id)
.AfterMap((s, d) => d.ParentId = parentId)
.AfterMap((s, d) => d.ScheduleNo = scheduleNo)
.AfterMap((s, d) => d.SchName = schName));
var mapper = config.CreateMapper();
schedule = mapper.Map<Schedule>(this);
I would prefer not to use the first four lines of my example but instead have them included using a conventional AutoMapper lambda expression. Possible?
I'd just use mapping to an existing object:
var existingSchedule = new Schedule()
{
Id = 12,
ParentId = 34,
ScheduleNo = 56,
SchName = "Foo",
};
var schedule = mapper.Map(this, existingSchedule);
And in the configuration leave the Ignore() lines but remove those with AfterMap() as they are no longer needed:
var config = new MapperConfiguration(
cfg => cfg.CreateMap<FinancialBase, Schedule>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.ParentId, opt => opt.Ignore())
.ForMember(d => d.ScheduleNo, opt => opt.Ignore())
.ForMember(d => d.SchName, opt => opt.Ignore()));
This question already has an answer here:
The Include path expression must refer to a navigation property defined on the type.in eager loading
(1 answer)
Closed 3 years ago.
Exception
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
This is the call causing this. Vignette is a Collection, so in my opinion this statement should be valid.
public ICollection<CarSharingEntry> GetAllCarSharingEntriesByUserSAM(string userSAM)
{
try
{
using (var _dbContext = new CarSharingContext())
{
_dbContext.Configuration.LazyLoadingEnabled = false;
return _dbContext.CarSharingEntries
.Include(e => e.ShareMeeting)
.Include(e => e.SelectedOptions)
.Include(e => e.SharedCar)
// Code Block causing this v
.Include(e => e.SharedCar.Vignette
.Select(v => new
{
v.Id,
v.GUID,
v.CountryName,
v.CountryCode
})
)
// ---------------------------
.Include(e => e.SharedCar.VehicleType)
.Include(e => e.SharedCar.Equipment)
.Include(e => e.SharedCar.FuelType)
.Include(e => e.SharedCar.Location)
.Include(e => e.CarSharer.Select(c => c.ContactDetails))
.Where(e => e.SharedCar.isForCarSharing)
// Commented out for debugging
//.Where(e => e.CarSharer.Any(p => p.SAM == userSAM))
.ToList();
}
}
catch (Exception ex)
{
throw ex;
}
}
What am I missing here?
You cannot include a select with an anonymous type:
.Include(e => e.SharedCar.Vignette
.Select(v => new
{
v.Id,
v.GUID,
v.CountryName,
v.CountryCode
})
)
Instead, include the related object directly:
.Include(e => e.SharedCar.Select(s => s.Vignette))
If you wish to only get some of the values from Vignette, you could do this by adding a .Select(x => new CarSharingEntry { ... }) and specifying what you want to do with each item before the ToList()
.Select(e => new CarSharingEntry {
ShareMeeting = e.ShareMeeting,
SelectedOptions = e.SelectedOptions,
SharedCar = new SharedCar {
Vignette = e.SharedCar.Vignette.Select(v => new {
v.Id,
v.GUID,
v.CountryName,
v.CountryCode
}),
VehicleType = e.SharedCar.VehicleType,
Equipment = e.SharedCar.Equipment,
// etc, etc...
},
}).ToList()
Let´s say I have
class Product
{
string name;
List<Order> orders;
}
class Order
{
string name;
}
If I try to map analyzers to Product name it work, but not for Order.name
//This work and adds the analyzer on the mapping list.
var resp = client.Map<Product>(map => map
.Properties(props => props
.String(s =>s
.Name(p => p.Name)
.IndexAnalyzer("normalize")
)
));
//This does not.
var resp = client.Map<Product>(map => map
.Properties(props => props
.String(s =>s
.Name(p => p.orders.First().Name)
.IndexAnalyzer("normalize")
)
));
Am I doing something wrong, or is this a bug?
Some more info:
Those classes are just an example to show the problem.
If I add [ElasticProperty(Analyzer = "normalize")] on the variable it works.
Actually the setup look something like Product Inherists from BaseProdcuts and BaseProductis is the one who has the List
As answered here, .Name(p => p.Orders.First().Name) is telling ES to map the field 'Name' on the Product document. Instead, you want to map to the 'Name' field on Orders, which is an array in your Product document.
Try this instead:
client.Map<Product>(m => m
.Properties(pp => pp
// Map Product.Name
.String(s => s
.Name(p => p.Name)
.IndexAnalyzer("normalize")
)
// Map Product.Orders.Name
.Object<List<Order>>(o => o
.Name(p => p.Orders)
.Properties(op => op
.String(s => s
.Name(os => os.First().Name)
.IndexAnalyzer("normalize"))))
));
I'm using automapper and I would like to know if it's possible to ignore a mapping of a field when that's null.
That's my code:
.ForMember(dest => dest.BusinessGroup_Id,
opt => opt.MapFrom(src => (int)src.BusinessGroup))
src.BusinessGroup type = "enum"
dest.BusinessGroup_Id = int
The objective it's to ingore that Mapping if src.BusinessGroup = null.
I think NullSubstitute option will do the trick
.ForMember(d => d.BusinessGroup_Id, o => o.MapFrom(s => (int?)s.BusinessGroup));
.ForMember(d => d.BusinessGroup_Id, o => o.NullSubstitute(0));
BTW you can write your conditions in mapping action:
.ForMember(d => d.BusinessGroup_Id,
o => o.MapFrom(s => s.BusinessGroup == null ? 0 : (int)s.BusinessGroup));
UPDATE if you cannot assign some default value to your property, you can just ignore it and map only not nulls:
.ForMember(d => d.BusinessGroup_Id, o => o.Ignore())
.AfterMap((s, d) =>
{
if (s.BusinessGroup != null)
d.BusinessGroup_Id = (int)s.BusinessGroup;
});