How do I map Objects in this scenario using c# AutoMapper - c#

This is the Source Object I want to map
public class Post
{
public string PostId { get; set; }
public string PostTitle { get; set; }
public virtual List<Comment> Comments { get; set; }
}
This destination object i want to map the source to
public class PostResponse
{
public int PostId { get; set; }
public string PostTitle { get; set; }
public IEnumerable<CommentObj> Comments { get; set; }
}
This is the Controller throwing the error
AutoMapper.AutoMapperMappingException: Error mapping types.
[HttpGet(ApiRoutes.Posts.GetAll)]
public async Task<IActionResult> GetAll()
{
var posts = await _postServices.GetPostsAsync();
return Ok(_mapper.Map<List<PostResponse>>(posts));
}
This is the Service
public async Task<List<Post>> GetPostsAsync()
{
var queryable = _dataContext.Posts.AsQueryable();
var psts = await queryable.Include(x => x.Comments).ToListAsync();
return psts;
}
This is the Mapping Profile
public DomainResponseProfile()
{
CreateMap<Post, PostResponse>().
ForMember(dest => dest.Comments, opt => opt.MapFrom(src => src.Comments.Select(x => new CommentResponse
{ PostId = x.PostId, DateCommented = x.DateCommented })));
}
This is the Domain Comment Object
public class Comment
{
public int CommentId { get; set; }
public int PostId { get; set; }
}
This is the Response Comment Object
public class CommentResponse
{
public int CommentId { get; set; }
public List<CommentObj> Comments { get; set; }
}

I just found out what I did wrong.
wrong
CreateMap<Post, PostResponse>().
ForMember(dest => dest.Comments, opt => opt.MapFrom(src => src.Comments.Select(x => new CommentResponse
{ PostId = x.PostId, DateCommented = x.DateCommented })));
right
CreateMap<Post, PostResponse>().
ForMember(dest => dest.Comments, opt => opt.MapFrom(src =>
src.Comments));

Related

AutoMapper - Problem in mapping models which the subfields are different objects

Via AutoMapper, I want to convert CreateUserInputModel to UserModel.
CreateUserInputModel has a property: List<int> Options which accepts IDs of options. UserModel has a property: List<OptionModel> Options which contains the list of OptionModel which has the field Id. I tried to create a mapper ForMember, but when I add it to the mapper, an unusual error appears without exception.
If you have any ideas on how to resolve this mapping, I will be very grateful. Thank you!
CreateUserInputModel
public class CreateUserInputModel
{
public string Email { get; set; } = string.Empty;
public string Firstname { get; set; } = string.Empty;
public string Lastname { get; set; } = string.Empty;
public DateTime EmploymentDate { get; set; }
public int WorkTypeId { get; set; }
public List<int>? Options { get; set; } = new List<int>();
}
UserModel
public class UserModel
{
public int Id { get; set; }
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Firstname { get; set; } = string.Empty;
public string Lastname { get; set; } = string.Empty;
public int VacationDays { get; set; }
public DateTime EmploymentDate { get; set; }
public WorkTypeModel WorkType { get; set; } = new WorkTypeModel();
public List<OptionModel>? Options { get; set; } = new List<OptionModel>();
}
User mapper
CreateMap<UserModel, CreateUserInputModel>()
.ForMember(dest => dest.WorkTypeId, opt => opt.MapFrom(src => src.WorkType.Id))
.ForMember(dest => dest.Options, opt => opt.MapFrom(src => src.Options.Select(option => option.Id).ToList()))
.ReverseMap();
Think that you miss out on the mapping configuration for mapping from int to OptionModel and vice versa.
CreateMap<int, OptionModel>()
.AfterMap((src, dest) =>
{
dest.Id = src;
});
CreateMap<OptionModel, int>()
.ConstructUsing(src => src.Id);
Sample .NET Fiddle

Entity Framework Core v3 : Eliminating repeating data

I am using Entity Framework Core v3. I am currently trying to retrieve data from tables that contain a one-to-many relationship.
The tables here are PersonNote and PersonNoteAttachment.
One PersonNote can have many PersonNoteAttachments. Data is getting currently repeated. So if you see the result section it has Name appearing twice. This is just an example. I am not sure if I need to change the query or change the model structure etc. Could somebody help
PersonNote
Id Name
-------------
113 TestNote
PersonNoteAttachment
Id PersonNoteId Note
---------------------------------
101 113 Attachment1
102 113 Attachment2
Result
TestNote Attachment1
TestNote Attachment2
What I am looking at is
TestNote Attachment1
Attachment2
Query
public IQueryable<PersonNote> GetPersonNotes(int personId)
{
var personNotes = _context.PersonNotes
.Include(x => x.Person)
.Include(x => x.Author)
.Include(x => x.PersonNoteAttachment)
.Where(p => p.PersonId == personId)
.OrderByDescending(d => d.Created);
return personNotes;
}
Model
namespace Organisation.Models.DataModels
{
[Table(nameof(PersonNote), Schema = "common")]
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
[ForeignKey("PersonId")]
public Person Person { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public PersonNoteAttachment PersonNoteAttachment { get; set; }
[ForeignKey("AuthorId")]
public Person Author { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
public class PersonNoteAttachment
{
public int Id { get; set; }
public int PersonNoteId { get; set; }
[ForeignKey("PersonNoteId")]
public PersonNote PersonNote { get; set; }
public string Alias { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }
public int Deleted { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
Mapping
CreateMap<Organisation.Models.DataModels.PersonNote, Organisation.Models.User.PersonNote>()
.ForMember(t => t.Id, opt => opt.MapFrom(s => s.Id))
.ForMember(t => t.PersonId, opt => opt.MapFrom(s => s.PersonId))
.ForMember(t => t.AuthorName, opt => opt.MapFrom(s => s.Author.FirstName + " " + s.Author.LastName))
.ForMember(t => t.FileName, opt => opt.MapFrom(s => s.PersonNoteAttachment.FileName))
.ForMember(t => t.MimeType, opt => opt.MapFrom(s => s.PersonNoteAttachment.MimeType))
.ForMember(t => t.Alias, opt => opt.MapFrom(s => s.PersonNoteAttachment.Alias))
.ForMember(t => t.Note, opt => opt.MapFrom(s => s.Note))
.ForMember(t => t.AuthorId, opt => opt.MapFrom(s => s.AuthorId))
.ForMember(t => t.CreatedBy, opt => opt.MapFrom(s => s.CreatedBy))
.ForMember(t => t.Created, opt => opt.MapFrom(s => s.Created));
If you notice PersonNoteAttachment is not an array. If I make it an array, then the following changes would need to be done:
public PersonNoteAttachment[] PersonNoteAttachment { get; set; }
and in the mapping to avoid compile errors
.ForMember(t => t.FileName, opt => opt.MapFrom(s => s.PersonNoteAttachment[0].FileName))
API
[FunctionName(nameof(GetPersonNote))]
[UsedImplicitly]
public Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "person-note/{id}")] HttpRequest req,
int id) => _helper.HandleAsync(async () =>
{
// await _helper.ValidateRequestAsync(req, SecurityPolicies.ViewNotes);
var personNotes = await _organisationRepository.GetPersonNotes(id).ProjectTo<PersonNote>(_mapper.ConfigurationProvider).ToListAsync();
return new OkObjectResult(personNotes);
});
ViewModel
namespace Organisation.Models.User
{
[Table(nameof(PersonNote), Schema = "common")]
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public string Alias { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }
public string AuthorName { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
}
}
Solution 1 - But with internal exception
Unable to cast object of type 'System.Boolean' to type 'System.Int32'.
Tried the following , which is providing the desired results . The postman returns 200k. Although debugging the code throwing an internal exception.
Added
Datamodel
namespace Organisation.Models.DataModels
{
public class PersonNote
{
public IEnumerable<PersonNoteAttachment> PersonNoteAttachment { get; set; }
}
}
Viewmodel
namespace Organisation.Models.User
{
[Table(nameof(PersonNote), Schema = "common")]
public class PersonNote
{
public IEnumerable<string> Alias { get; set; }
public IEnumerable<string> FileName { get; set; }
public IEnumerable<string> MimeType { get; set; }
}
}
Mapping
.ForMember(t => t.FileName, opt => opt.MapFrom(s => s.PersonNoteAttachment.Select(x=> x.FileName)))
.ForMember(t => t.MimeType, opt => opt.MapFrom(s => s.PersonNoteAttachment.Select(x => x.MimeType)))
.ForMember(t => t.Alias, opt => opt.MapFrom(s => s.PersonNoteAttachment.Select(x => x.Alias)))
Abduls solution
API
[FunctionName(nameof(GetPersonNote))]
[UsedImplicitly]
public Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "person-note/{id}")] HttpRequest req,
int id) => _helper.HandleAsync(async () =>
{
await _helper.ValidateRequestAsync(req, SecurityPolicies.ViewNotes);
var personNotes = _organisationRepository.GetPersonNotes(id);
//IEnumerable<PersonNote> personNotes = groupedNotes.Select(Profiles.GenistarUserProfile.MapGroupToPersonNote);
return new OkObjectResult(personNotes.ToList());
});
Repository
public IQueryable<Genistar.Organisation.Models.User.PersonNote> GetPersonNotes(int personId)
{
var personNotes = _context.PersonNotes
.Include(x => x.Person)
.Include(x => x.Author)
.Where(p => p.PersonId == personId).Select(x => new Genistar.Organisation.Models.User.PersonNote
{
//assign all properties
Attachments = _context.PersonNotesAttachments.Where(y => y.PersonNoteId == x.Id).Select(y => new Genistar.Organisation.Models.User.PersonNoteAttachment
{
FileName = y.FileName,
Alias = y.Alias,
MimeType = y.MimeType
}),
PersonId = x.PersonId,
AuthorName = x.Person.FirstName + " " + x.Person.LastName,
Note = x.Note,
Id = x.Id,
Created = x.Created
}).OrderByDescending(x=> x.Created).AsNoTracking();
return personNotes;
}
The following code will allow you to use a 3 layered architecture with your app, and try to reduce the coupling between your layers while keeping your existing structure.
To start, change your query:
public IQueryable<DataModel.PersonNote> GetPersonNotes(int personId)
{
var personNotes = _context.PersonNotes
.Include(x => x.Person)
.Include(x => x.Author)
.Where(p => p.PersonId == personId);
return personNotes;
}
And add another repo query:
public IQueryable<DataModel.PersonNoteAttachment> GetAttachments(int personNoteId)
{
var attachments = _context.PersonNotesAttachments.Where(x => x.Id == personNoteId);
return attachments ;
}
Use the following Models, PersonNote being your ViewModel:
namespace Organisation.Models.User
{
public class PersonNoteAttachment
{
public string Alias { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }
}
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public IEnumerable<PersonNoteAttachment> Attachments { get; set; }
public string AuthorName { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
}
}
Now we create a new function in BL layer that will return your data for the ViewModel:
public IEnumerable<User.PersonNote> GetPersonNotesAndAttachments(int personID)
{
var personNotes = _organisationRepository.GetPersonNotes(id).Select(x =>
new User.PersonNote
{
//assign all properties
Attachments = _organisationRepository.GetAttachments(x.PersonNoteID).Select(y =>
new User.PersonNoteAttachment
{
FileName = y.FileName,
Alias = y.Alias,
MimeType = y.MimeType
}).AsEnumerable(),
PersonId = x.PersonId,
AuthorName = x.Person.FirstName + " " + x.Person.LastName,
Note = x.Note,
Id = x.Id,
Created = x.Created
});
return personNotes;
}
The IEnumerable<PersonNote> "What I am looking at is" can be accessed like this:
IEnumerable<User.PersonNote> personNotes = new BlClass().GetPersonNotesAndAttachments(id);

EF Working With One To Many Models With Composite Keys

I'm trying to learn .Net Core 2.1 and testing a little project with already existing database. We have 3 tables (Template, TemplateDesc, TemplateParameter) with one of them has one-to-many relationship. When I get one Template with controller, it returns null for TemplateDescriptions and ParameterValues. Also if I try to delete Template, it returns FK exception. Can someone point the problem with the below codes?
Note : I use Swagger extension to test codes.
public class Template
{
public decimal CompanyCode { get; set; }
public string TemplateCode { get; set; }
public List<TemplateDesc> TemplateDescriptions { get; set; }
public DateTime TemplateDate { get; set; }
public string RuleCode { get; set; }
public string SourceTypeCode { get; set; }
public string Description { get; set; }
public bool IsBlocked { get; set; }
public string CreatedUserName { get; set; }
public DateTime CreatedDate { get; set; }
public List<TemplateParameter> ParameterValues { get; set; }
}
public class TemplateDesc
{
public decimal CompanyCode { get; set; }
public string TemplateCode { get; set; }
public string LangCode { get; set; }
public string TemplateDescription { get; set; }
}
public class TemplateParameter
{
public decimal CompanyCode { get; set; }
public string TemplateCode { get; set; }
public string TemplateRuleCode { get; set; }
public string ParameterName { get; set; }
public string ParameterValue { get; set; }
}
modelBuilder.Entity<Template>(entity =>
{
entity.HasKey(e => new { e.CompanyCode, e.TemplateCode });
entity.HasMany(e => e.TemplateDescriptions).WithOne(e => e.Template).HasForeignKey(e => new { e.CompanyCode, e.TemplateCode });
entity.HasMany(e => e.ParameterValues).WithOne(e => e.Template).HasForeignKey(e => new { e.CompanyCode, e.TemplateCode });
}
modelBuilder.Entity<TemplateDesc>(entity =>
{
entity.HasKey(e => new { e.CompanyCode, e.TemplateCode, e.LangCode });
entity.HasOne(e => e.Template).WithMany(e => e.TemplateDescriptions).HasForeignKey(e => new { e.CompanyCode, e.TemplateCode }).OnDelete(DeleteBehavior.Cascade);
}
modelBuilder.Entity<TemplateParameter>(entity =>
{
entity.HasKey(e => new { e.CompanyCode, e.TemplateCode, e.TemplateRuleCode, e.ParameterName});
entity.HasOne(e => e.Template).WithMany(e => e.ParameterValues).HasForeignKey(e => new { e.CompanyCode, e.TemplateCode}).OnDelete(DeleteBehavior.Cascade);
}
[HttpGet]
public ActionResult<Template> GetWithKey([FromQuery] decimal companyCode, [FromQuery] string templateCode)
{
try
{
var template = this.mRepository.Find(e => e.CompanyCode == companyCode && e.TemplateCode.AreEqual(templateCode)).FirstOrDefault();
if (template == null)
return new JsonResult(new ApiResponse<Template>(ResponseType.Exception, null));
return new JsonResult(new ApiResponse<Template>(ResponseType.Success, template));
}
catch
{
throw;
}
}
[HttpDelete]
public ActionResult DeleteWithKey([FromQuery] decimal companyCode, [FromQuery] string templateCode)
{
if (this.mRepository.Find(e => e.CompanyCode == companyCode && e.TemplateCode.AreEqual(templateCode)).Count() < 1)
return new JsonResult(new ApiResponse<string>(ResponseType.NotFound, templateCode));
this.mRepository.Delete(companyCode, templateCode);
return new JsonResult(new ApiResponse<Template>(ResponseType.Success, null));
}
You have to either use lazy loading, or use Include construct:
db.Templates
.Include(x => x.TemplateDesc)
.Include(x => x.TemplateParameter)
.FirstOrDefault(...)
the includes can be put in extension method:
public IQueryable<Template> BuildTemplate(this DbSet<Template> set)
{
return set.Include(x => x.TemplateDesc)
.Include(x => x.TemplateParameter);
}
Then you can use
dbContext.Templates.BuildTemplate.FirstOrdefault(x => x.TemplateDescriptions.Any(td => td.TemplateCode == "xyz"));

C# AutoMapper with Entity Framework - Works with List but not single nullable instance

I have a very wierd error that I can't get my head around. I'm using AutoMapper 6 with AutoMapper.Collection and AutoMapper.Collection.EntityFramework.
https://github.com/AutoMapper/AutoMapper.Collection
As you can see from the screenshot below, every component is updated apart from Image that is null for updatedContact. If I however do an explicit mapping for only updatedImage it works. It also works to update a collection of images without a problem. Has anyone experienced this? Other single properties works as well but for some reason Image is causing trouble.
//Works
var updatedArticle = Mapper.Map<ArticleViewModel, Article>(articleVm, articleOriginal);
//Every component is updated a part from Image.
var updatedContact = Mapper.Map<ContactViewModel, Contact>(contactVm, contactOriginal);
//Works
var updatedImage = Mapper.Map<ImageViewModel, Image>(contactVm.Image);
//Works
var newContact = Mapper.Map<ContactViewModel, Contact>(contactVm);
Mapping:
cfg.CreateMap<ArticleViewModel, Article>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id);
cfg.CreateMap<ImageViewModel, Image>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForSourceMember(x => x.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.ImageBytes, opt => opt.MapFrom(src => Encoding.ASCII.GetBytes(src.Image)));
cfg.CreateMap<ContactViewModel, Contact>(MemberList.Source)
.EqualityComparison((src, dst) => src.Id == dst.Id)
.ForSourceMember(x => x.IsDeleted, opt => opt.Ignore())
.ForSourceMember(x => x.FullName, opt => opt.Ignore());
Files:
public class ArticleViewModel
{
public int Id { get; set; }
...
public List<ImageViewModel> Images { get; set; }
}
public class Article : IEntity<int>
{
public int Id { get; set; }
...
public virtual ICollection<Image> Images { get; set; }
}
public class ContactViewModel
{
public int Id { get; set; }
...
public ImageViewModel Image { get; set; }
}
public class Contact: IEntity<int>
{
[Key]
public int Id { get; set; }
...
public int? ImageId { get; set; }
public Image Image { get; set; }
}
public class ImageViewModel
{
public int Id { get; set; }
public string Image { get; set; }
public string ImageType { get; set; }
public bool IsDeleted { get; set; }
}
public class Image : IEntity<int>
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public byte[] ImageBytes { get; set; }
public string ImageType { get; set; }
public int? ArticleId { get; set; }
public virtual Article Article { get; set; }
}
Finally solved it, I had forgot to mark Image as virtual in Contact. After doing that everything started working out of the box.
public virtual Image Image { get; set; }
I think you need to tell your contact mapper in the config to explicitly use the mapping for the image vm. There might be one or two typos as I'm doing this from memory but it should be similar to:
.ForMember(x => x.Image, opt => opt.MapFrom(contact => Mapper.Map<ImageViewModel, Image>(contact.ImageVm);))

ASP.Net C# MVC Issue Mapping Domain Model to ViewModel using AutoMapper

I'm having a little difficulty mapping a domain model to a view model, using AutoMapper.
My controller code is:
//
// GET: /Objective/Analyst
public ActionResult Analyst(int id)
{
var ovm = new ObjectiveVM();
ovm.DatePeriod = new DateTime(2013, 8,1);
var objectives = db.Objectives.Include(o => o.Analyst).Where(x => x.AnalystId == id).ToList();
ovm.ObList = Mapper.Map<IList<Objective>, IList<ObjectiveVM>>(objectives);
return View(ovm);
}
I am getting an error on the ovm.ObList = Mapper.... (ObList is underlined in red with the error):
'ObList': cannot reference a type through an expression; try 'Objectives.ViewModels.ObjectiveVM.ObList' instead
My Objective Class is:
public class Objective
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public Analyst Analyst { get; set; }
}
My ObjectiveVM (view model) is:
public class ObjectiveVM
{
public DateTime DatePeriod { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
In my startup/global.asax.cs I have used AutoMapper to map the Objective to the ObjectiveVM:
Mapper.CreateMap<Objective, ObjectiveVM.ObList>()
.ForMember(dest => dest.Include, opt => opt.Ignore())
.ForMember(dest => dest.AnalystName, opt => opt.MapFrom(y => (y.Analyst.AnalystName)));
Any help would be much appreciated,
Mark
Ok, thanks for all the suggestions - what I've ended up with is:
Controller:
//
// GET: /Objective/Analyst
public ActionResult Analyst(int id)
{
var ovm = new ObjectiveVM().obList;
var objectives = db.Objectives.Include(o => o.Analyst).Where(x => x.AnalystId == id).ToList();
ovm = Mapper.Map<IList<Objective>, IList<ObjectiveVM.ObList>>(objectives);
var ovm2 = new ObjectiveVM();
ovm2.obList = ovm;
ovm2.DatePeriod = new DateTime(2013, 8,1);
return View(ovm2);
}
ViewModel:
public class ObjectiveVM
{
public DateTime DatePeriod { get; set; }
public IList<ObList> obList { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
CreateMap:
Mapper.CreateMap<Objective, ObjectiveVM.ObList>()
.ForMember(dest => dest.Include, opt => opt.Ignore())
.ForMember(dest => dest.AnalystName, opt => opt.MapFrom(y => (y.Analyst.AnalystName)))
;
If I've mis-understood any advice, and you provided the answer, please post it - and I'll mark it as such.
Thank you,
Mark
As the commenter nemesv has rightly mentioned, the problem is about
ovm.ObList = Mapper.Map<IList<Objective>, IList<ObjectiveVM>>(objectives);
ObList is not a member of ObjectiveVM so, you should change the ObjectiveVM like this:
public class ObjectiveVM
{
public DateTime DatePeriod { get; set; }
public IList<ObList> obList { get; set; }
public class ObList
{
public int ObjectiveId { get; set; }
public int AnalystId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string AnalystName { get; set; }
public bool Include { get; set; }
}
}
Update:
Controller:
public ActionResult Analyst(int id)
{
var ovm = new ObjectiveVM { DatePeriod = new DateTime(2013, 8, 1) };
var objectives = db.Objectives.Include(
o => o.Analyst).Where(x => x.AnalystId == id).ToList();
ovm.obList = Mapper.Map<IList<Objective>,
IList<ObjectiveVM.ObList>>(objectives);
return View(ovm);
}

Categories