Automapper and NHibernate: lazy loading - c#

I have the following scenario.
public class DictionaryEntity
{
public virtual string DictionaryName { get; set; }
public virtual IList<DictionaryRecordEntity> DictionaryRecord { get; set; }
}
public class DictionaryDto
{
public string DictionaryName { get; set; }
public IList<DictionaryRecordEntity> DictionaryRecord { get; set; }
}
I'm using Automapper and NHibernate. In NHibernate the DictionaryRecord property is marked as lazy loaded.
When I make the mapping from DictionaryEntity -> DictionaryDto, Automapper loads all my DictionaryRecords.
But I don't want this behavior, is there a way to configure the Automapper in order to don't resolve this property until I really access this property.
My workaround for this situation consists of splitting the DictionaryEntity in 2 classes and create a second Automapper mapping.
public class DictionaryDto
{
public string DictionaryName { get; set; }
}
public class DictionaryDtoFull : DictionaryDto
{
public IList<DictionaryRecordEntity> DictionaryRecord { get; set; }
}
and then in the code, depending on the need, call AutoMapper.Map appropriately.
return Mapper.Map<DictionaryDto>(dict);
return Mapper.Map<DictionaryDtoFull>(dict);
Does somebody have a better solution for my problem?

You must add a condition to validate if the collection is initialized to be mapped. You can read here more details: Automapper: Ignore on condition of.
AutoMapper.Mapper.CreateMap<DictionaryEntity, DictionaryDto>()
.ForMember(dest => dest.DictionaryRecord, opt => opt.PreCondition(source =>
NHibernateUtil.IsInitialized(source.DictionaryRecord)));

You could ignore the property, if this is of use?
AutoMapper.Mapper.CreateMap<DictionaryEntity, DictionaryDto>()
.ForMember(dest => dest.DictionaryRecord,
opts => opts.Ignore());
http://cpratt.co/using-automapper-mapping-instances/

Related

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

Getting related data via an AutoMapper mapping?

I want to create a mapping between this entity model:
public class ProductType
{
public int Id { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public ICollection<ProductIdentifierInType> Identifiers { get; set; }
public ICollection<ProductPropertyInType> Properties { get; set; }
public ICollection<Product> Products { get; set; }
}
... and this viewmodel:
public class ViewModelProductType
{
public int Id { get; set; }
public string Title { get; set; }
public int SortOrder { get; set; }
public IList<ViewModelProductIdentifier> Identifiers { get; set; }
public IList<ViewModelProductProperty> Properties { get; set; }
public ICollection<ViewModelProduct> Products { get; set; }
}
... but since the Identifiers and Properties are not of the same type in the viewmodel as in the entity model, it won't work directly, like this:
CreateMap<ProductType, ViewModelProductType>();
I don't want to change my models too much. In the entity model, I need the Identifiers and Properties to be respectively ProductIdentifierInType and ProductPropertyInType, because there are many-to-many relationships there, which requires linking tables.
But in the viewmodel, I need Identifiers and Properties to be the full objects in order to display their properties in the view.
Is there a way to accomplish this with mapping? Maybe using .ForPath() to get the two objects' properties?
Assuming you have defined the direct entity to view model mappings:
CreateMap<ProductIdentifier, ViewModelProductIdentifier>();
CreateMap<ProductProperty, ViewModelProductProperty>();
Now it would be enough to extract the corresponding member using LINQ Select inside MapFrom expression. The important thing to know is that AutoMapper does not require the type of the returned expression to match the type of the destination. If they don't match, AutoMapper will use the explicit or implicit mappings for that types.
CreateMap<ProductType, ViewModelProductType>()
.ForMember(dst => dst.Identifiers, opt => opt.MapFrom(src =>
src.Identifiers.Select(link => link.Identifier)))
.ForMember(dst => dst.Properties, opt => opt.MapFrom(src =>
src.Properties.Select(link => link.Property)))
;
I think what you are looking for is a Custom Value Resolver.
There you can explicitly specify how Auto Mapper should map one object to another.
In your case it could look something like this:
public class CustomResolver : IValueResolver<ProductType, ViewModelProductType, IList<ViewModelProductIdentifier>>
{
public int Resolve(ProductType source, ViewModelProductType destination, IList<ViewModelProductIdentifier> destMember, ResolutionContext context)
{
// Map you source collection to the destination list here and return it
}
}
You can then pass/inject the resolver when calling CreateMap, i.e.:
CreateMap<ProductType, ViewModelProductType>()
.ForMember(dest => dest.Identifiers, opt => opt.ResolveUsing<CustomResolver>());
Analogously, do the same for your 'Properties' property.
Note that I did not debug this but merely adapted the examples provided in the link above.

Automapper - How to map from source child object to destination

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));

Automapper source prefix property

We are designing a temporal system where the definition of an entity can change. I am trying to setup Automapper but can't quite work out how the prefix should work.
As an example, I would have the following entity:
public class ReferenceDataDefinition
{
public string Name { get; set; }
}
public class ReferenceData
{
public int Id { get; set; }
public ReferenceDataDefinition Current { get; set; }
}
With the following DTO:
public class ReferenceDataDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
I know I can use
CreateMap<ReferenceData, ReferenceDataDTO>()
.ForMember(p => p.Id, o => o.MapFrom(s => s.Id)
.ForMember(p => p.Name, o => o.MapFrom(s => s.Current.Name);
But I feel there must be something smarter I can do?
I've tried adding RecognizePrefixes("Current") but that had no effect.
I've tried adding RecognizePrefixes("Current")
This isn't how prefix are used. They are for a scenario where your properties start with a prefix (often because of a database naming schema).
For example, If you had the following classes:
public class ReferenceData
{
public int Ref_Id { get; set; }
public string Ref_Name { get; set; }
}
public class ReferenceDto
{
public int Id { get; set; }
public string Name { get; set; }
}
You could recognize the following prefix:
cfg.RecognizePrefixes("Ref_");
AutoMapper would then be able to map those two objects without you having to define specific mappings with .ForMember.
Regarding you own mapping, since both Id properties on ReferenceData and ReferenceDataDTO have the same name, you should be able to remove the Id member mapping as AutoMapper can infer it automatically:
CreateMap<ReferenceData, ReferenceDataDTO>()
.ForMember(p => p.Name, o => o.MapFrom(s => s.Current.Name);
This should suffice.
As for .Current using Flattening you could remove it if you would change your DTO class to rename it to CurrentName.
Please check this documentation:
Recognizing pre/postfixes
Also the RecognizePrefixes works for source object prefixes
Use RecognizeDestinationPrefixes method
Check these previous posts:
AutoMapper with prefix
https://github.com/AutoMapper/AutoMapper/issues/421

Categories