Order list with automapper attributes - automapper

I'm currently defining my map like this:
CreateMap<Request, RequestDetailsDTO>()
.ForMember(dto => dto.Approvals, conf => conf.MapFrom(x => x.ApprovalStatus.OrderBy(x => x.Position)));
but now I'm wanting to switch to using attributes on the RequestDetailsDTO instead. Is it possible to specify the ordering with an attribute, or does that require I stick with the fluent version?
For example, it would be nice to be able to do something like this:
[AutoMap(typeof(Request))]
public class RequestDetailsDTO {
[SourceMember(nameof(ApprovalStatus), OrderBy = nameof(ApprovalStatus.Position)]
public IEnumerable<RequestApprovalStatusDTO> Approvals { get; set; } = default!;

Related

AutoMapper not mapping composite object

I am using AutoMapper 6.2.2 to map a composite object (made up of two objects) into one, and it isn't working.
Source/destination types
Source type: a composite object (using suggestion from https://github.com/AutoMapper/AutoMapper/issues/389)
public class JoinedJob
{
public JobInfo job { get; set; }
public LocationInfo loc { get; set; }
}
Destination type:
public class JobDetailsModel
{
public int JobID { get; set; }
public string JobType { get; set; }
}
For testing purposes, I am using this very simple destination class, ignoring the source loc fields completely for now.
Mapping configuration
CreateMap<JoinedJob, JobDetailsModel>()
.ForMember(dest => dest.JobID, opt => opt.MapFrom(src => src.job.JobID))
.ForMember(dest => dest.JobType, opt => opt.MapFrom(src => src.job.JTName));
Expected behavior
I expect the JoinedJob object to get properly mapped to the JobDetailsModel object.
Actual behavior
This doesn't work at all when using ProjectTo<>. The objects I got back had null and zero values.
Steps to reproduce
So I tested a single instance:
var testDetail = _mapper.Map<JoinedJob, JobDetailsModel>(testJob);
and this exception is thrown:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type.
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters.
JoinedJob -> JobDetailsModel (Destination member list)
NDSWebAPI.Modules.Core.BusinessLogic.JobRetrieval+JoinedJob -> NDSWebAPI.Models.Core.JobDetailsModel (Destination member list)
Unmapped properties:
JobID
JobType
It's as if AutoMapper doesn't recognize the .ForMember mappings I defined?
Should this work? I have no problems at all with mapping single objects (e.g. mapping JobInfo to JobDetailsModel works). The problem is introduced with the composite object.

How to map multiple list to a single list?

I have few classes and they have multiple list items like below:
public class Request1
{
public List<AdditionalApplicantData> AdditionalApplicantData { get; set;}
public List<ApplicantData> ApplicantData { get; set; }
}
public class Request2
{
public List<ApplicantDetails> ApplicantData { get; set; }
}
I want to map Request1 to Request2 but List of ApplicantData has to be mapped from multiple sources like List of ApplicantData & List of AdditionalApplicantData but not sure how to achieve it can someone please help me here?
You can use function below with createMap() function. Source: https://github.com/AutoMapper/AutoMapper/wiki/Before-and-after-map-actions
.AfterMap((src, dest) => {
dest.ApplicantData = /*your logic here*/
});
And you should mark ApplicantData as don't map because you have a variable named ApplicantData at the source class. You should implement the logic yourself.
EDIT:
When you are initializing mapper, you create map for each object. So for your case it would be like:
Mapper.Initialize(cfg => {
cfg.CreateMap<Request1, Request2>()
.ForMember(x => x.ApplicantData, opt => opt.Ignore()) //You want to implement your logic so ignore mapping
.AfterMap((src, dest) =>
{
dest.ApplicantData = /*implement your logic here*/
});
});
public class ApplicantDetailsResolver : IValueResolver<Request1, Request2, List<ApplicantDetails>>
{
public List<ApplicantDetails> Resolve(Request1 source, Request2 destination,List<ApplicantDetails> destMember, ResolutionContext context)
{
destination.ApplicantDetails = context.Mapper.Map<List<ApplicantDetails>>(source.ApplicantData);
for (int i = 0; i < destination.ApplicantDetails.Count(); i++)
{
context.Mapper.Map(source.AdditionalApplicantData.ElementAt(i), destination.ApplicantDetails.ElementAt(i));
}
return destination.ApplicantDetails;
}
}
I have written above custom value resolver for mapping list from multiple sources and its working fine but problem, is it can't match properties which are differently named, is there way I can handle this scenario as well?

Projecting from a model with an integer property to class with Enum property

I have my model classes set up with the integer properties just as they are stored in the database. So a sample model might look like:
public class TaskModel
{
public int TaskId { get; set; }
public int TaskStatus { get; set; }
}
But on my actual business classes I want to use enums, so the matching business class would look like:
public class Task
{
public int TaskId { get; set; }
public Status TaskStatus { get; set; }
}
I then want to use Automapper's LINQ projection features to query these business classes, like:
return db.Tasks.Where( t => t.TaskStatus == 1 ).Project().To<Task>();
But when I do this I get this error:
Unable to create a map expression from System.Int32 to MyNamespace.TaskStatus
I've been able to resolve it by setting up the mapping as such:
Mapper.CreateMap<TaskModel, Task>()
.ForMember(t => t.TaskStatus, opt => opt.MapFrom(m => (TaskStatus)m.TaskStatus))
.ReverseMap();
This seems to work (so far), but my question is there a better or DRYer way to do this. The problem is I will need to do this for a ton of properties across a ton of models and classes. Seems like there should be a simpler way to do what is essentially a simple cast with having to write 100's of lines of mapping code.
You can do this with a type converter:
Mapper.CreateMap<int, TaskStatus>()
.ProjectUsing(src => (TaskStatus)src);
This will be used everywhere. The reason you have to do this is because some LINQ providers have different ways of dealing with enum conversions and persistence, so you have to use the right expression it expects (and AutoMapper doesn't assume it knows what EF or NHibernate or whatever need).

Automapper: map List<string> to List<Class>

How can I map a List<string> to List<Class>?
Usecase: from the Webservice I'm getting a class with a list of string but in my MVC Viewmodel, I want to have Class instead with a single property, which has the value of the string. That way I can add Validation attributes to the property.
I have the way how I convert the List into a List, however I can't get the other way around to work.
Any simple solutions?
The way to do this with AutoMapper is to use .ConstructUsing:
Mapper.CreateMap<string, Class>()
.ConstructUsing(str => new Class { MyProp = str });
Example: https://dotnetfiddle.net/0Vlc8b
You can easily do it with Linq.
var newList = oldList.Select(x => new Item(x)).ToList();
You could do this:
void Main()
{
AutoMapper.Mapper.CreateMap<string,A>()
.ForMember(a => a.Name, m => m.MapFrom(s => s));
new[] {"A", "B"}.Select (AutoMapper.Mapper.Map<A>).Dump();
}
class A
{
public string Name { get; set; }
}
(Linqpad code)
But I think this can go down as a textbook example of over-engineering. Just do it as in Daniel's example.

Fluent Nhibernate Decimal Primary key with Auto Increment

Is it Possible to created Decimal as a Primery key with Auto Incremented from FNH Mapping??
I ran across the same thing. It appears that fluent-nhibernate is enforcing this constraint.
If you know that the database will allow this (in this case, it should be fine) you can just bypass the constraint like so:
Id(x => x.MyDecimalId).GeneratedBy.Custom<IdentityGenerator>();
This will use the same strategy as Identity() would have, but not raise an exception. In my case, I've added an extension method on IdentityGenerationStrategyBuilder<IdentityPart> as such:
public static class IdentityPartExtensions
{
public static void NumericIdentity(this IdentityGenerationStrategyBuilder<IdentityPart> identityBuilder)
{
identityBuilder.Custom<IdentityGenerator>();
}
}
So then you can just do:
Id(x => x.MyDecimalId).GeneratedBy.NumericIdentity();
To achieve auto increment Identity type must be integral int, long, uint, ulong.
ex :
public class User
{
public virtual int Id { get; set; }
public virtual IList Names { get; set; }
}
Id(x => x.ID).Column("ID").GeneratedBy.Increment();
for decimal properties :
Id(x => x.ID).Column("ID").GeneratedBy.Assigned();
Assign the value while creating a new object. Value should be increment of last generated key in your database.
OR you can just implement a Custom ID Generator for NHibernate.
http://lucisferre.net/2009/07/14/implementing-a-custom-id-generator-for-nhibernate/

Resources