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.
Related
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!;
I'm using automapper with CQRS pattern. Below is my class which takes input from .net core API. The API takes collection as input and I'm sending collection in my Mediatr Command object. In Mediatr command, I'm mapping source collection to destination collection and while doing mapping, I'm getting following exception:
AutoMapper.AutoMapperMappingException
HResult=0x80131500
Message=Error mapping types.
Inner Exception 1:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I'm using follwing code for mapping:
var insertData = _mapper.Map<List<Source>, List<Destination>>(request.Data.ToList());
In my class, I have following:
public class Source: ICustomMapping
{
public int? Prop1 { get; set; }
public string Prop2 { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<Destination, Source>()
.ForMember(dto => dto.Prop1 , opt => opt.MapFrom(p => p.Prop1 ))
.ForMember(dto => dto.Prop2, opt => opt.MapFrom(p => p.Prop2))
;
}
}
This mapping works flawlessly when I have single object in both ways (forward and reverse). Now I need to pass collection of object for processing and save destination collection data in to database.
After looking in to the documentation I realize that I do not have the reverse mapping.
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<Destination, Source>()
.ForMember(dto => dto.Prop1 , opt => opt.MapFrom(p => p.Prop1 ))
.ForMember(dto => dto.Prop2, opt => opt.MapFrom(p => p.Prop2))
.ReverseMap();
}
The ReverseMap() I was missing.
Thanks
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).
I'm using an automapper to flatten the object coming from WS. Simplified model would be as follows:
public abstract class AOrder {
public Product Product {get;set;}
public decimal Amount {get;set;}
//number of other properties
}
public abstract class Product {
//product properties
}
public class RatedProduct : Product {
public int Rate { get;set;}
}
public class MarketOrder : AOrder {
//some specific market order properties
}
Using automapper I'm trying to flatten this into:
public class OrderEntity {
public decimal Amount {get;set;}
public int ProductRate {get;set;}
}
with next mapping:
CreateMap<RatedProduct, OrderEntity>();
CreateMap<MarketOrder, OrderEntity>();
The above mapping will not map the ProductRate.
Atm I've just used the AfterMap:
CreateMap<MarketOrder, OrderEntity>()
.AfterMap((s,d) => {
var prod = s.Product as RatedProduct;
if (prod != null)
{
//map fields
}
});
which works pretty well, but thought if I could reuse the automapper flattening possibilities (i.e. matching by name) I wouldn't need to apply the after map in quite many places.
Note: I can't change the WS and this is just a tiny part from object hierarchy.
Advice appreciated.
Mapping Rate to ProductRate is fairly straight forward with "ForMember"
The one where you have to do a cast to the specific type to see if it is that type is a little trickier but I think the same approach you took is what you might have to do however I don't think you need to do "aftermap". I thought all your destination mappings had to be found OR you need to mark them as ignore of the mapping will fail.
Another thing you could do is just change the OrderEntity.ProductRate to be OrderEntity.Rate. Then it would find it and map it for you except where it was hidden because Product doesn't have a rate (but RatedProducts do).
public class OrderEntity {
public decimal Amount {get;set;}
public int Rate {get;set;} //changed name from ProductRate to just Rate.
}
Mapper.CreateMap<Product, OrderEntity>()
.Include<RatedProduct, OrderEntry>();
Mapper.CreateMap<RatedProduct, OrderEntry>();
SEE: Polymorphic element types in collections
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/