Automapper - How to map from source child object to destination - c#

I am trying to map from a child object of source to destination(as parent object).
Source Model:
public class SourceBaseResponse<T> where T : new()
{
public string Type { get; set; }
public string Id { get; set; }
public T Attributes { get; set; }
}
For my example I am using T to be of type SourceAssignment
public class SourceAssignment
{
public string Id { get; set; }
public string Email { get; set; }
public string EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
Destination Object
public class DestinationAssignment
{
public string Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I want to map Source Model directly to Destination. So, I was trying to use
CreateMap<SourceAssignment, DestinationAssignment>();
CreateMap<SourceBaseResponse<SourceAssignment>, DestinationAssignment>()
.ForMember(dest => dest, opt => opt.MapFrom(src => AutoMapperConfig.Mapper.Map<DestinationAssignment>(src.Attributes)));
This is not working as I am getting run time error in the above line that "Custom configuration for members is only supported for top-level individual members on a type."
So, as per this thread I tried the following
CreateMap<SourceBaseResponse<SourceAssignment>, DestinationAssignment>()
.AfterMap((src, dst) => Mapper.Map(src.Attributes, dst));
Now, I am getting error where mapping should happen which says "Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance."
I am able to use ForMember for each property and map it from src.Attributes to dest(For eg: src.Attribute.Id to dest.Id). This works, but I do not really want to do this as my Source are complex classes involving nested childs(as this is a Web API response and I do not have control over this). So a lot of custom mapping is done here
CreateMap<SourceAssignment, DestinationAssignment>();
Any suggestions on how to proceed.

Resolution context is needed to be able to call Mapper.Map(), you can get resolution context by using ConstructUsing():
CreateMap<SourceChild, Destination>();
CreateMap<Source, Destination>()
.ConstructUsing((src, ctx) => ctx.Mapper.Map<Destination>(src.SourceChild));

Related

Automapper Ignore nested property

I've search the internet for a few hours now and can't seem to figure out any solutions for myself, or understand some of other similar answers I'm finding.
All I'm trying to do is ignore property from a nested object in my AutoMapper. Here's a small overview of the Models I'm working with (I removed some properties to make them a bit smaller for the purposes of this question).
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class ProductDto
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string LabelColor { get; set; }
public DateTime Created { get; set; }
}
public class CategoryDto
{
public int CategoryId { get; set; }
public string Name { get; set; }
public string LabelColor { get; set; }
}
Basically all I want is my automapper to ignore the Created property coming from the Category class anytime a Product is queried via API. The closest I've to achieving this is by having the whole Category object ignored when queried.
Here is the current mapping configuration for my Product class
public class ProductMapping: Profile
{
public ProductMapping()
{
CreateMap<Product, ProductDto>()
.ReverseMap()
.ForMember(x => x.ProductId, o => o.Ignore());
}
}
I was able to null out the whole object by putting .ForPath(x => x.Category.Created, o => o.Ignore() before .ReverseMap()
I should note that of course the classes and mapper class are distributed through multiple files and the CategoryMapping class looks the same as the ProductMapping. It is removing the Created property, though that is expected.
If anyone can help isolate my issue, or demonstrate a better way to achieve this I am open to suggestions. Till then I will continue trying to figure out this issue. Thanks for any help!
If I understand correctly if you want to ignore the Created field from Category class then you should maybe put the ignore logic when mapping from CategoryDto -> Category or vice versa and mapping from ProductDto <-> remains the same.
CreateMap<Product, ProductDto>()
.ReverseMap()
CreateMap<Category, CategoryDto>()
.ReverseMap()
.ForMember(x => x.Created, o => o.Ignore());
May have answered my own question, but what I had to do was switch the Data type in my ProductDto from Category to CategoryDto. I was under the assumption that Automapper would sort of take care of that itself.
Sorry for that! Thank you to person who took the time to give me an answer aswell!

Create Map for IEnumerable to IEnumerable /List in automapper

I am having issue with Automapper when i have a collection.
Below is my situaion
Bussiness Model
public class Member
{
public int MemberId { get; set; }
public int TeamID { get; set; }
public Team Team { get; set; }
}
and i want to return below model from controller
public class Member
{
public int MemberId { get; set; }
public string Team { get; set; }
public int TeamId { get; set; }
}.
I have trying for something like below but not Team string remains null. I beleive i have to specify the mapping. But i am new to automapper i can't figure it out.
IEnumerable<Models.Member> ienumerableDest = _mapper.Map<IEnumerable<Entities.Member>, List<Models.Member>>(members);
Here is the Response
My guess is you haven't specified anywhere how to map your classes.
In your Entities.Member class you have a Team property of type Team, but you are trying to map it to string, so it fails.
In your configuration, add a profile which specifies this mapping, like this:
public class AppProfile : Profile
{
public AppProfile()
{
CreateMap<Entities.Member, Model.Member>(MemberList.Destination)
.ForMember(d => d.Team, opt => opt.MapFrom(src => src.Team.Name));
}
}
In this example i assumed a property "Name" in your Team object.
And in your startup class you should register your profile in your AutoMapper configuration.

Mapping nested lists with automapper

This is my class setup. How do i map only Invalid=false for DTOReportObservation AND DTOReportObservationLocation items?
reports = Mapper.Map<List<Report>, List<DTOReport>>(userReports);
public class DTOReport
{
public List<DTOReportObservation> Observations;
}
public class DTOReportObservation
{
public Guid ReportObservationID { get; set; }
public Guid ReportID { get; set; }
public bool Invalid { get; set; }
public List<DTOReportObservationLocation> ObservationLocations;
}
public class DTOReportObservationLocation
{
public Guid ReportObservationLocationID { get; set; }
public Guid ReportObservationID { get; set; }
public bool Invalid { get; set; }
}
CreateMap<Report, DTOReport>(MemberList.Source)
.ForMember(d => d.Observations, opt => opt.MapFrom(src => src.ReportObservations))
//??ReportObservations.Locations
With automapper you shouldn't need to create maps of lists. You just create a map from one type to another and let automapper iterate over the collections.
Can you also clarify what you mean by Invalid=false seeing as Invalid is a guid type.
For mapping only when invalid is false you can use the conditional mapping. https://automapper.readthedocs.io/en/latest/Conditional-mapping.html .
For more info on lists see here in the docs about collections. https://automapper.readthedocs.io/en/latest/Lists-and-arrays.html

AutoMapper does not unproxy NHibernate entity

Consider this entity:
public class CondRule
{
public virtual decimal Id { get; set; }
public virtual string Name { get; set; }
public virtual CondRuleType RuleType { get; set; }
public virtual string Statement { get; set; }
}
and CondRuleType is:
public class CondRuleType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
It is obvious that there is a one to one relation between CondRule and CondRuleType entities.
Also I have CondRuleDto:
public class CondRuleDto
{
public decimal Id { get; set; }
public string Name { get; set; }
public CondRuleType RuleType { get; set; }
}
I have mapped CondRule to CondRuleDto using AutoMapper:
Mapper.CreateMap<CondRule, CondRuleDto>();
When I call Session.Get to get CondRule by id and the map the result to CondRuleDto, AutoMapper does not resolve proxies (here RuleType).
Here is my code:
var condRule = Session.Get<CondRule>(id);
var condRuleDto = Mapper.Map<CondRuleDto>(condRule);
When I watch condRuleDto, RuleType property is a NHibernate proxy. I want AutoMapper to map RuleType proxy to a POCO. How to make this work?
PS: I have to mention that when I use query and use automapper's Project, it will result a list with no proxies (I know that Project make this happen. May be I need something like Project to use after Session.Get):
Session.Query<CondRule>().Project().To<CondRuleDto>().ToList()
Casts won't change the underlying object (i.e. your CondRuleType will be still a proxy even if you map its instance to another property of type CondRuleType).
It seems like you need to create a custom mapping where CondRule.RuleType is mapped creating a new instance of CondRuleType.

How to avoid circular loop in Linq to Sql query?

Currently I am using [JsonIgnore] property to avoid circular references in the models. But I think, it will be applied in all implementations.
Right now I am using like this-
I have two Models:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string ProjectName { get; set; }
public DateTime? EstimatedEndDate { get; set; }
public DateTime? ProjectStartDate { get; set; }
public string OrderNumber { get; set; }
public string ProjectDescription { get; set; }
public virtual List<ServiceObject> ServiceObjects { get; set; }
[Required]
public string RegardingUser { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
public bool IsProjectDeleted { get; set; }
[NotMapped]
public int? ServiceObjectTemplateID { get; set; }
}
One another Model is-
public class ServiceObject
{
public int ServiceObjectID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string RegardingUser { get; set; }
public int? ParentServiceObjectID { get; set; }
[ForeignKey("ParentServiceObjectID")]
public virtual List<ServiceObject> ServiceObjects { get; set; }
public int ProjectID { get; set; }
[JsonIgnore]
public virtual Project Project { get; set; }<---------------------------
public virtual List<ServicePicture> ServicePictures { get; set; }
public bool IsServiceObjectDeleted { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
}
But I want to apply JsonIgnore property or any other property In Linq to Sql query itself. I mean I want to apply it dynamically.
The Query is like-
var projectList = (from pro in context.Projects.All
select pro).ToList();
I want to apply conditionally.
Thanks in advance.
XY problem. Your actual problem is "I want to control at runtime what properties get serialized".
The most common method of controlling what properties are serialized is using the JsonIgnoreAttribute. However as others have commented, attributes are applied at compile time, and cannot normally be modified at runtime (nor would you want to, since it would in effect be a global setting being changed at runtime, you would run into a ton of Threading issues).
Instead, the answer is to use the IContractResolver interface to change the serialization behavior.
public class OmitPropertyContractResolver
: IContractResolver
{
private readonly string[] _ignoredProperties;
private readonly IContractResolver _resolver;
public OmitPropertyContractResolver(IContractResolver resolver, params string[] ignoredProperties)
{
_ignoredProperties = ignoredProperties;
_resolver = resolver;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = _resolver.CreateProperties(type, memberSerialization);
return properties
.Where(p => _ignoredProperties.Contains(p.Name) == false)
.ToList();
}
}
The other possibility is to use ReferenceLoopHandling setting in your Json.net settings.
var serializer = new JsonSerializer(
new JsonSerializerSetting()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
} );
This will serialize each object only once.
The last option is to use ItemReferenceLoopHandling. This is a non-standard way of serializing (and deserializing) Object-Graphs (note, graph and not tree) which have circular references.
The disadvantage of this method is that it is non-standard, and might not work on the client end.
Ok, so based on your inputs and as I suspected, what you're seeing is a typical EF Code First issue. EF generates dynamic proxies for each of your entity so that it can track changes to it. However, this makes serialization difficult since they are created as dynamic objects. There are few ways to avoid this:
Option A: Disable proxy creation just for the particular query:
// disable proxy creation just for the duration of query
context.Configuration.ProxyCreationEnabled = false;
// do the query, serialize, go crazy!
// enable proxy again. or if your context goes out of scope after this call, then you can ignore re-enabling.
context.Configuration.ProxyCreationEnabled = true;
Option B: If you are not going to modify and update the entity at some later point
var entity = context.Where(...); // or whatever query
// detach entity
context.Entry(entity).State = EntityState.Detached;
Option C: Avoid proxies by modifying type definition:
If you create sealed type or type without virtual properties, EF will not create a proxy since there
is nothing for the proxy to do.
public sealed class Project {...}
// or make it non virtual
...
public List<ServiceObject> ServiceObjects { get; set; }
...
Note that in this case, you have to load related objects manually:
context.Projects.Include(p => p.ServiceObjects).ToList();
Option D: Disable proxy creation permanently:
// in your initializer or DbContext class' constructor
DbContext.Configuration.ProxyCreationEnabled = false;
Note: If proxy creation is disabled, EF will not tracked changes automatically. You can manually track them using the Snapshot method.
You can follow one or more of these options depending on your design. I usually stick with option A or B.

Categories