AutoMapper condition Mapping Issue - c#

I have got a problem using Automapper when conditionally Mapping a table.
Here is an example:
public class DepositsVm : IMapFrom<Deposits>
{
public long DepId { get; set; }
public AddressDto Address { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<Deposits, DepositsVm>()
.ForMember(d => d.DepId, s => s.MapFrom(s => s.DepId))
.ForMember(d => d.Address, opt => opt.MapFrom(s => s.ProcessingId != null ? s.DataProcessing.GridCollect.Grid.Address : s.Reduction.DataCollect.Tower.Address));
}
}
This results in an Object reference error.
I can see we can use https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions but this allow to check for only one condition. I expect to map a table using different join condition in failure and success scenarios.
But this works, because I'm projecting each Address separately. But, this is not desired. Because both are from Same Address Table
public class DepositsVm : IMapFrom<Deposits>
{
public long DepId { get; set; }
public AddressDto GridAddress { get; set; }
public AddressDto TowerAddress { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<Deposits, DepositsVm>()
.ForMember(d => d.DepId, s => s.MapFrom(s => s.DepId))
.ForMember(d => d.GridAddress, opt => opt.MapFrom(s => s.DataProcessing.GridCollect.Grid.Address));
.ForMember(d => d.TowerAddress, opt => opt.MapFrom(s => s.Reduction.DataCollect.Tower.Address));
}
}

Either some part of s.DataProcessing.GridCollect.Grid.Address is null, or some part of s.Reduction.DataCollect.Tower.Address is null

Related

Automapper - Projecting multiple properties from conditionally selected list

I want to avoid duplicating the src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First() from the below mapping. Without changing the shape of my destinationDto.
I'm aware that I could change the DestinationDto to hold a "StatusDto" object which could then have it's own projection defined, and achieve it that way.
Does automapper have some syntax to do this without having to create extra dto's that reflect the source structure?
Basically a way to say
var status = src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First()
and then use that status across multiple .ForCtorParam
The source looks like this:
Source 1 -- 0..N Status
Status 0..N -- 1 StatusType
StatusType 0..N -- 1 StatusGroup
public class DestinationDto
{
public DestinationDto(...)
public int Id { get; set; }
public DateTime StatusDate { get; set; }
public string Open { get; set; }
public string Status { get; set; }
public DateTime Created { get; set; }
}
public class DestinationProfile : Profile
{
public DestinationProfile()
{
CreateProjection<SourceType, DestinationDto>(MemberList.Destination)
.ForCtorParam(nameof(DestinationDto.Id), opt => opt.MapFrom(src => src.Id))
.ForCtorParam(nameof(DestinationDto.StatusDate),
opt => opt.MapFrom(src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First().CreatedUTC))
.ForCtorParam(nameof(DestinationDto.Open),
opt => opt.MapFrom(src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First().StatusType!.IsOpen))
.ForCtorParam(nameof(DestinationDto.Status),
opt => opt.MapFrom(src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First().StatusType!.Label))
.ForCtorParam(nameof(DestinationDto.Created), opt => opt.MapFrom(src => src.CreatedUTC));
}
}

How to map this using AutoMapper 11.0.0?

I have a model classes shown below.
public class ModelA
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string DetailId{ get; set; }
public string DetailName { get; set; }
public string DetailDescription { get; set; }
}
public class ModelB
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class ModelC
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ModelB Detail { get; set; }
}
Now, I want to map ModelA to ModelC where
DetailId = Detail.Id
DetailName = Detail.Name
DetailDescription = Detail.Description
Is this possible using Automapper 11.0.0 ?
If possible, how ?
var config = new MapperConfiguration(cfg => {
cfg.CreateMap< ModelA, ModelC >().ForMember(u => u. Detail, o => o.MapFrom(s => s));
});
You want the modelB mapping in modelC,But the structure of modelA is different from that of modelC.
Use this code:
CreateMap<ModelA, ModelC>()
.ForMember(d => d.Detail.Id, src => src.MapFrom(e => e.DetailId))
.ForMember(d => d.Detail.Description, src => src.MapFrom(e => e.DetailDescription))
.ForMember(d => d.Detail.Name, src => src.MapFrom(e => e.DetailName))
.ReverseMap();
Thanks for the answers.
However I solved it already.
MapperConfiguration _config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ModelA, ModelB>()
.ForMember(d => d.DetailId, opt => opt.MapFrom(s => s.Id))
.ForMember(d => d.DetailName , opt => opt.MapFrom(s => s.Name))
.ForMember(d => d.DetailDescription, opt => opt.MapFrom(s => s.Description))
.ReverseMap();
cfg.CreateMap<ModelC, ModelA>()
.ForMember(d => d.DetailId, opt => opt.MapFrom(s => s.Detail.Id ))
.ForMember(d => d.DetailName , opt => opt.MapFrom(s => s.Detail.Name ))
.ForMember(d => d.DetailDescription, opt => opt.MapFrom(s => s.Detail.Description ))
cfg.CreateMap<ModelA, ModelC>()
.ForMember(d => d.Detail, opt => opt.MapFrom(s => Mapper.Map<ModelA, ModelB>(s)))
});
UPDATE
Found a better solution.
Check out the link below.
https://dotnettutorials.net/lesson/mapping-complex-type-to-primitive-type-using-automapper/

AutoMapper, mapping one type in two different types

I have the following models:
public class Stuff
{
...
public IList<Place> Places { get; set; } = null!;
...
}
public class Place
{
...
public IList<Stuff> Stuffs { get; set; } = null!;
...
}
public class StuffEntity
{
...
public IList<PlaceStuffEntity> Places { get; set; } = null!;
...
}
public class PlaceEntity
{
...
public IList<PlaceStuffEntity> Stuffs { get; set; } = null!;
...
}
public class PlaceStuffEntity
{
public int StuffId { get; private set; }
public StuffEntity Stuff { get; set; } = null!;
public int PlaceId { get; private set; }
public PlaceEntity Place { get; set; } = null!;
}
cfg.CreateMap<StuffEntity, Stuff>()
.ForMember(d => d.Places,
opt => opt.MapFrom(s => s.Places.Select(y => y.Place).ToList()));
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(d => d.Stuffs,
opt => opt.MapFrom(s => s.Places.Select(y => y.Stuff).ToList()));
cfg.CreateMap<PlaceAndStuffEntity, Stuff>() // < -- Issue
.IncludeMembers(entity=> entity.Stuff);
cfg.CreateMap<PlaceAndStuffEntity, Place>() // < -- Issue
.IncludeMembers(entity=> entity.Place);
by some reason when I add both last lines, conversion does not work ...
But if I add only one line for example for converting PlaceAndStuffEntity -> Stuff works only one conversion from PlaceEntity -> Place
var place = mapper.Map<Place>(placeEntity); // <- This works
var stuff = mapper.Map<Stuff>(stuffEntity); // <- Does not work !!
Is there a way properly handle the following conversions ?
It sounds like you want to map through the joining table (PlaceAndStuff) to get to the other entity type. For instance in your Place to get a list of Stuff, and Stuff to get a list of Place, you want to direct Automapper how to navigate through the joining table.
For instance:
cfg.CreateMap<StuffEntity, Stuff>()
.ForMember(x => x.Places, opt => opt.MapFrom(src => src.PlaceEntity));
// Where StuffEntity.Places = PlaceAndStuffEntities, to map Stuff.Places use PlaceAndStuffs.PlaceEntity
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(x => x.Stuffs, opt => opt.MapFrom(src => src.StuffEntity));
So rather than trying to tell EF how to map the joining entity PlaceStuffEntity, we focus on the PlaceEntity and StuffEntity, and tell Automapper to navigate through the joining entity to get at the actual Stuff and Place relatives via the joining entity.
Change
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(d => d.Stuffs,
opt => opt.MapFrom(s => s.Places.Select(y => y.Stuff).ToList()));
to
cfg.CreateMap<PlaceEntity, Place>()
.ForMember(d => d.Stuffs,
opt => opt.MapFrom(s => s.Stuffs.Select(y => y.Stuff).ToList()));
Source type PlaceEntity does not have a property named Places, only Stuffs.

Automapper - How to map to IEnumerable

I'm making Forum system, I have SubCategoryThreadsViewModel in which I'm trying to map LastComment and Date of last post for every thread. This is my code:
public class SubCategoryThreadsViewModel : IHaveCustomMappings
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<Thread> Threads { get; set; }
public ThreadInfoSubCategoryViewModel ThreadInfoSubCategoryViewModel { get; set; }
public void CreateMappings(IConfiguration configuration)
{
configuration.CreateMap<Thread, SubCategoryThreadsViewModel>()
.ForMember(m => m.Title, opt => opt.MapFrom(t => t.SubCategory.Title))
.ForMember(m => m.Description, opt => opt.MapFrom(t => t.SubCategory.Description))
.ForMember(m => m.Threads, opt => opt.MapFrom(t => t.SubCategory.Threads))
.ForMember(m => m.ThreadInfoSubCategoryViewModel, opt => opt.MapFrom(t => new ThreadInfoSubCategoryViewModel()
{
LastCommentBy = t.Posts.Select(a => a.Author.UserName),
DateOfLastPost = t.Posts.Select(a => a.CreatedOn.ToString()),
}))
.ReverseMap();
}
The code
.ForMember(m => m.ThreadInfoSubCategoryViewModel, opt => opt.MapFrom(t => new ThreadInfoSubCategoryViewModel()
{
LastCommentBy = t.Posts.Select(a => a.Author.UserName),
DateOfLastPost = t.Posts.Select(a => a.CreatedOn.ToString()),
}))
is working but only when property ThreadInfoSubCategoryViewModel is not Ienumerable as in the code above, and inside are two IEnumerable strings.
public class ThreadInfoSubCategoryViewModel
{
public IEnumerable<string> LastCommentBy { get; set; }
public IEnumerable<string> DateOfLastPost { get; set; }
}
This works, but I want ThreadInfoSubCategoryViewModel to be Ienumerable, and in the class properties to be string for easy foreach.
I have tried to make it IEnumerable, but with current automapper code it doesn't work.
You need to manually map the member to an IEnumerable<ThreadInfoSubCategoryViewModel> rather than a single object.
I assume each Post in t.Posts represents one ThreadInfoSubCategoryViewModel, so a simple Select() should do it:
public IEnumerable<ThreadInfoSubCategoryViewModel> ThreadInfoSubCategoryViewModel { get; set; }
...
.ForMember(m => m.ThreadInfoSubCategoryViewModel, opt => opt.MapFrom(t =>
t.Posts.Select(p => new ThreadInfoSubCategoryViewModel()
{
LastCommentBy = p.Author.UserName,
DateOfLastPost = p.CreatedOn.ToString()
})
))

AutoMapper settings properties to null on destination object

I have something like this:
public class DomainEntity
{
public string Name { get; set; }
public string Street { get; set; }
public IEnumerable<DomainOtherEntity> OtherEntities { get; set; }
public IEnumerable<DomainAnotherEntity> AnotherEntities { get; set; }
}
public class ApiEntity
{
public string Name { get; set; }
public string Street { get; set; }
public int OtherEntitiesCount { get; set; }
}
And following mapper configuration:
Mapper.Configuration.AllowNullCollections = true;
Mapper.CreateMap<DomainEntity, ApiEntity>().
ForSourceMember(e => e.OtherEntities, opt => opt.Ignore()).
ForSourceMember(e => e.AntherEntities, opt => opt.Ignore()).
ForMember(e => e.OtherEntitiesCount, opt => opt.MapFrom(src => src.OtherEntities.Count()));
Mapper.CreateMap<ApiEntity, DomainEntity>().
ForSourceMember(e => e.OtherEntitiesCount, opt => opt.Ignore()).
ForMember(e => e.OtherEntities, opt => opt.Ignore()).
ForMember(e => e.AnotherEntities, opt => opt.Ignore());
To get the ApiEntity from the DomainEntity I'm using var apiEntity = Mapper.Map<DomainEntity, ApiEntity>(myDomainEntity);
To get the merged DomainEntity from an ApiEntity I'm using var domainEntity = Mapper.Map(myApiEntity, myDomainEntity);
But when using this, the properties OtherEntities and AnotherEntities are set to null - even when they had values before calling the mapping from myApiEntity to myDomainEntity. How can I avoid this so they really merge and not just replacing values?
Thanks for any help.
I think you're looking for UseDestinationValue instead of Ignore:
Mapper.CreateMap<ApiEntity, DomainEntity>().
ForSourceMember(e => e.OtherEntitiesCount, opt => opt.UseDestinationValue()).
ForMember(e => e.OtherEntities, opt => opt.UseDestinationValue()).
ForMember(e => e.AnotherEntities, opt => opt.UseDestinationValue());

Categories