I'm trying to use Automapper v3 to map from my Post struct to my Postmodel class. I need to map Term Name to my Categories array but only if the Type equals "Category".
Here's my code
public class NewsModel
{
public NewsModel(int id)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Post, PostModel>();
});
Posts = new List<PostModel> {Mapper.Map<PostModel>(_newsGetter.GetItem(id))};
}
public List<PostModel> Posts { get; set; }
}
Map to this class
public class PostModel
{
public String[] Categories { get; set; }
}
Map from this Struct
public struct Post
{
public Term[] Categories { get; set; }
}
public Struct Term
{
public string Name{ get; set; }
public string Type{ get; set; }
}
Any help is greatly appreciated.
The simplest solution would be to map the filtered categories from the Post to the PostModel.
cfg.CreateMap<Post, PostModel>()
.ForMember(pm => pm.Categories
, o => o.MapFrom(p => p.Categories.Where(t => t.Name != "Category")));
Then simply convert the Term to a string using ConvertUsing
cfg.CreateMap<Term, string>().ConvertUsing(t => t.Name);
Related
I have a situation where I need to map a sub-collection of items within an object to a collection of items in another object. I am essentially trying to flatten the object for use by a consuming system.
Given the following entity classes:
public class PersonEntity
{
public int Id { get; set; }
public virtual ICollection<OutcomeEntity> Outcomes { get; set; }
}
public class OutcomeEntity
{
public int Id { get; set; }
public bool Outcome { get; set; }
public virtual ICollection<GradeEntity> Grades { get; set; }
public PersonEntity Person { get; set; }
}
public class GradeEntity
{
public int Id { get; set; }
public string Grade { get; set; }
public string MarkersComment { get; set; }
public OutcomeEntity Outcome { get; set; }
}
I need to map the OutcomeEntity and GradeEntity to the following flattened structure where there can be many outcomes, containing many different grades:
public class PersonDTO
{
public int PersonId { get; set; }
public virtual ICollection<GradeDTO> Grades { get; set; }
}
public class GradeDTO
{
public int OutcomeId { get; set; }
public int GradeId { get; set; }
public string Grade { get; set; }
public string MarkersComment { get; set; }
}
Basically, for every Outcome in the collection, I want to iterate over the grades within it and create a new object (GradeDTO).
I have attempted to create a basic map, but I simply cannot get my head around the sub-properties.
To create one collection from many you can use SelectMany extension method. With this method and the following configuration AutoMapper will create PersonDto from PersonEntity.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<GradeEntity, GradeDTO>()
.ForMember(dto => dto.GradeId, x => x.MapFrom(g => g.Id))
.ForMember(dto => dto.OutcomeId, x => x.MapFrom(g => g.Outcome.Id));
cfg.CreateMap<PersonEntity, PersonDTO>()
.ForMember(dto => dto.PersonId, x => x.MapFrom(p => p.Id))
.ForMember(dto => dto.Grades, x => x.MapFrom(p => p.Outcomes.SelectMany(o => o.Grades)));
});
I have an entity as Plan with multiple sub-plans (children), each of which could be null.
For the PlanDto, I am trying to load up a list of all children rather than having a separate property for each child like the entity.
I have already achieved it manually through a foreach loop but now I am trying to do it via AutoMapper, which is failing for some reason.
Entities:
public class Plan
{
public virtual int Id { get; set; }
public DateTime Date { get; set; }
public virtual PlanDetail PlanChild1 { get; set; }
public virtual ObservationCare PlanChild2 { get; set; }
}
public class PlanDetail
{
public virtual int Id { get; set; }
public virtual Plan Plan { get; set; }
public virtual string Description { get; set; }
}
public class ObservationCare
{
public virtual int Id { get; set; }
public virtual Plan Plan { get; set; }
public virtual string Description { get; set; }
}
DTOs:
public class PlanDto: EntityDto
{
public DateTime Date { get; set; }
public IEnumerable<ChildPlan> ChildPlan { get; set; }
}
public class ChildPlan : EntityDto
{
public ChildPlanType Type { get; set; }
}
public enum ChildPlanType
{
PlanDetail,
ObservationCare
}
AutoMapper config:
configuration.CreateMap<Plan, PlanDto>();
configuration.CreateMap<PlanDetail, ChildPlan>()
.ForMember(dto => dto.Type, options => options.MapFrom(p => ChildPlanType.PlanDetail));
configuration.CreateMap<ObservationCare, ChildPlan>()
.ForMember(dto => dto.Type, options => options.MapFrom(p => ChildPlanType.ObservationCare));
Mapping attempt:
var output = new List<PlanDto>();
var plans = await _planRepository.GetAll().ToList();
foreach (var plan in plans)
{
output.Add(ObjectMapper.Map<PlanDto>(plan));
}
I do not know why ChildPlan DTOs in the output list are always null!
You have to specify the mapping for PlanDto.ChildPlan:
configuration.CreateMap<Plan, PlanDto>()
.ForMember(dto => dto.ChildPlan,
options => options.MapFrom(
p => new object[] { p.PlanChild1, p.PlanChild2 }.Where(c => c != null)));
If you are using Entity Framework Core, you have to use eager-loading:
var plans = await _planRepository.GetAll()
.Include(p => p.PlanChild1)
.Include(p => p.PlanChild2)
.ToList();
There's also a simpler and more efficient way to map a list:
var output = ObjectMapper.Map<List<PlanDto>>(plans);
I've got three classes:
public class UserReport : Entity
{
public string Name { get; set; }
public string Email { get; set; }
public List<string> Departments { get; set; }
public List<string> Titles { get; set; }
}
public abstract class Entity
{
public Guid Id { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateLastModified { get; set; }
public string CreatedBy { get; set; }
public string LastModifiedBy { get; set; }
public bool Active { get; set; }
protected Entity()
{
DateCreated = DateTime.Now;
DateLastModified = DateTime.Now;
Id = Guid.NewGuid();
}
}
public class UserModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Departments { get; set; }
public string Titles { get; set; }
}
With my automapper configs set as:
CreateMap<List<string>, string>().ConvertUsing(strings => {
if (strings != null)
return string.Join("\n", strings);
return "";
});
CreateMap<UserReport, UserModel>();
When trying to call from a generic method using the Automapper Ef Extensions:
IQueryable<TModel> modelQueryable = _reportService.GetReportBaseQuery().ProjectToQueryable<TModel>();
I get this error
Missing map from System.String to System.Char. Create using Mapper.CreateMap.
GetReportBaseQuery() returns an IQueryable<TReport>, so the UserReport in this instance. I don't see any char properties, why is this coming up?
Just for testing I tried to make one:
CreateMap<String, Char>().ConvertUsing(x => x.FirstOrDefault());
And then it says:
Argument types do not match
Further research shows that this works:
Mapper.Map<List<TReport>, List<TModel>>(_reportService.GetReportBaseQuery().ToList());
But I can't use that since I need it to be a queryable returned. So something is different when I try to do an EF projection, not sure what that is though. Writing a select statement from one to the other is easy enough, but that's not generic and re-usable.
The solution is to explicitly set mapping for the Departments and Titles fields:
CreateMap<UserReport, UserModel>()
.ForMember(x => x.Departments, o => o.MapFrom(s => string.Join("\n", s.Departments)))
.ForMember(x => x.Titles, o => o.MapFrom(s => string.Join("\n", s.Titles)));
But, this does not explain, why this situation occurs.
As DOTang pointed out:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable1[System.String])' method, and this method cannot be translated into a store expression.
The AutoMapper extension seems to try to map the following thing:
IEnumerable<string> => IEnumerable<char>
First of all I have these two models to store a post in two tables one for shared data and the other contains cultured data for English and Arabic
public class Post
{
public int Id { set; get; }
public bool Active { get; set; }
public bool Featured { get; set; }
public virtual ICollection<PostContent> Contents { get; set; }
}
public class PostContent
{
public int Id { set; get; }
public string Title { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
[StringLength(2)]
public string Culture { get; set; }
public int PostId { get; set; }
[InverseProperty("PostId")]
public virtual Post Post{ set; get; }
}
Mapping
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("Posts");
}
}
public class PostContentMap : EntityTypeConfiguration<PostContent>
{
public PostContentMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(p => p.Post).WithMany(p => p.Contents).HasForeignKey(p=>p.PostId);
ToTable("PostContents");
}
}
I have two questions
1- Is these models are connected properly. Is there something else I need to do ?
2- I need to select all Posts with their contents where the culture of the content 'en' for example. I used this:
var res = context.Posts.Include(p => p.Contents.Single(c => c.Culture.Equals("en")));
and have this error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.Parameter name: path
If you know you are not going to support more than two cultures then I would just add to your Post class.
public class Post
{
public Post()
{
Contents = new List<PostContent>();
}
public int Id { set; get; }
public bool Active { get; set; }
public bool Featured { get; set; }
public int? EnglishContentId { get;set;}
public int? ArabicContentId { get;set;}
PostContent EnglishContent {get;set;}
PostContent ArabicContent {get;set;}
}
public class PostContent
{
public int Id { set; get; }
public string Title { get; set; }
public string Summary { get; set; }
public string Details { get; set; }
[StringLength(2)]
public string Culture { get; set; }/*This property is not required*/
}
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("Posts");
HasOptional(p => p.EnglishContent).WithMany().HasForeignKey(p=>p.EnglishContentId);
HasOptional(p => p.ArabicContent).WithMany().HasForeignKey(p=>p.ArabicContentId);
}
}
public class PostContentMap : EntityTypeConfiguration<PostContent>
{
public PostContentMap()
{
HasKey(p => p.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("PostContents");
}
}
The Above design will simplify your design and queries, will improve the performance alot.
But if you might have to support more cultures then you got the design and mapping right.
As far as EF 5, include does not allow filters, but I am not sure about EF 6.0
atleast you can get all posts that have english contents as follows
Add using System.Data.Entity;
var res = context.Posts.Include(p => p.Contents).Where(c => c.Contents.Any(cp=>cp.Culture.Equals("en")));
Ok totally noob question about AutoMapper.
Employee model object.
public class Employee {
public string FirstName { get; set; }
public string LastName { get; set; }
public decimal HourlyRate { get; set; }
public List<string> EmailAddresses { get; set; }
}
Dto object:
public class UpdateEmployeeDetailsDto {
public decimal HourlyRate { get; set; }
public string EmailAddress1 { get; set; }
public string EmailAddress2 { get; set; }
}
I want to map the DTO to the Employee object and turn EmailAddress1 and EmailAddress2 into a List.
I've searched for this type of thing on google and SO but the expamples I've found I've not been able to translate them into working code.
Any help?
Use AfterMap to provide custom mapping (creation list from properties in your case):
Mapper.CreateMap<UpdateEmployeeDetailsDto, Employee>()
.AfterMap((dto, e) => e.EmailAddresses = new List<string>() {
dto.EmailAddress1,
dto.EmailAddress2
});
Apart from what #lazyberezovsky says, you can also create a custom resolver:
public class CustomResolver : ValueResolver<UpdateEmployeeDetailsDto, List<string>>
{
protected override List<string> ResolveCore(UpdateEmployeeDetailsDto source)
{
return new List<string> { source.EmailAddress1, source.EmailAddress2 }
}
}
Mapper.CreateMap<UpdateEmployeeDetailsDto, Employee>()
.ForMember(dest => dest.EmailAddresses, opt => opt.ResolveUsing<CustomResolver>());
What's the point? You can make it more generic and use this to resolve cases Value1, Value2, Value3...ValueN get mapped to List<ValueType> Values