Automapper, CustomMapping not loading fields of a virtual property - c#

I have the following classes.
public class SomeModel
{
[Key]
public int Id { get; set; }
[Required]
public string UserId { get; set; }
public virtual User User { get; set; }
[Required]
public string Name { get; set; }
}
And:
public class SomeModelDetailsResponseModel : IMapFrom<SomeModel>, IHaveCustomMappings
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
public void CreateMappings(IConfiguration configuration)
{
configuration.CreateMap<SomeModel, SomeModelDetailsResponseModel>("name").AfterMap((b, r) =>
{
r.UserName = b.User.FirstName + b.User.LastName;
});
}
}
For some reason, when I project an IQueryable of SomeModel to an IQueryable of SomeModelDetailsResponseModel the UserName property turns out to be null.

Assuming these are you class definitions:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class SomeModel
{
public int Id { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string Name { get; set; }
}
public class SomeModelDetailsResponseModel
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string UserName { get; set; }
}
Solution 1
Do your mapping like this:
var config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<SomeModel, SomeModelDetailsResponseModel>().AfterMap((b, r) =>
{
r.UserName = b.User.FirstName + b.User.LastName;
});
});
var mapper = config.CreateMapper();
var response = mapper.Map<SomeModel, SomeModelDetailsResponseModel>(new SomeModel()
{
User = new User()
{
FirstName = "FN",
LastName = "LN"
}
});
Since you have your input as IQueryable<SomeModel> and you want to project it into IQueryable<SomeModelDetailsResponseModel>, then you can do this:
var result = q.Select(m => mapper.Map<SomeModel, SomeModelDetailsResponseModel>(m));
where q is your IQueryable<SomeModel> instance.
Solution 2
If you want to use ProjectTo<>, then initialize your mapper as the following:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SomeModel, SomeModelDetailsResponseModel>()
.ForMember(r => r.UserName, c => c.MapFrom(o => o.User.FirstName + o.User.LastName));
});
Then, do your projection as this:
var result = q.ProjectTo<SomeModelDetailsResponseModel>().ToArray();
Where q is your IQueryable<SomeModel>.

Related

Web API C# .net: cannot convert the model in the dto in get method

As I said in the title, I'm trying to convert in the get method a model object to its DTO.
My method is to get users and is the next piece of code:
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDTO>>> GetUsers()
{
var users = _context.Users.ToList();
var userDtos = new List<UserDTO>();
foreach (var user in users)
{
userDtos.Add(new UserDTO
{
IdUser = user.UserProfessionId,
UserName = user.UserName,
UserCompany = user.UserCompany,
UserMail = user.UserMail,
UserProfession = user.UserProfession,
UserProfessionField = user.UserProfessionField
});
}
return userDtos;
}
These are my model and DTO for user:
namespace Sims.Models
{
public partial class User
{
public User()
{
DataUsages = new HashSet<DataUsage>();
}
public long IdUser { get; set; }
public int UserProfessionId { get; set; }
public int UserProfessionFieldId { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public byte[]? UserPicture { get; set; }
public virtual Profession UserProfession { get; set; } = null!;
public virtual ProfessionField UserProfessionField { get; set; } = null!;
public virtual ICollection<DataUsage> DataUsages { get; set; }
}
}
and
namespace sims.DTO
{
public partial class UserDTO
{
public long IdUser { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public virtual ProfessionDTO UserProfession { get; set; } = null!;
public virtual ProfessionFieldDTO UserProfessionField { get; set; } = null!;
}
}
Profession and ProfessionField are also models and have their own DTO. But in the get method, the two following lines contain the same error as it "cannot implicitly convert type '....Models.Profession' to '....DTO.ProfessionDTO'".
Do you have any idea ?
In case, here is an example of the Profession Model and DTO:
namespace Sims.Models
{
public partial class Profession
{
public Profession()
{
ProfessionFields = new HashSet<ProfessionField>();
Users = new HashSet<User>();
}
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
public virtual ICollection<ProfessionField> ProfessionFields { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
and
namespace sims.DTO
{
public class ProfessionDTO
{
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
}
}
Thanks for reading
The UserProfession property is of type ProfessionDTO:
public virtual ProfessionDTO UserProfession { get; set; } = null!;
But you're trying to populate it with an object of type Profession:
UserProfession = user.UserProfession,
Just as the error states, they are different types and can't be substituted for one another. Populate the property with an instance of ProfessionDTO instead:
UserProfession = new UserProfessionDTO
{
IdProfession = user.UserProfession.IdProfession,
ProfessionName = user.UserProfession.ProfessionName
},
If the user.UserProfession field is null then you'd need to check for that. For example:
UserProfession = user.UserProfession == null ?
null as UserProfessionDTO :
new UserProfessionDTO
{
IdProfession = user.UserProfession?.IdProfession,
ProfessionName = user.UserProfession?.ProfessionName
},

AutoMapper ForMember and MapFrom is not executed

ForMember/MapFrom is somehow not executed whatever I try.
Those are the classes to be mapped;
public class ImageParams : IEntityParams, IImage, IOperatorFields
{
public ImageParams()
{
}
public ImageParams(string userId, string title, string description, string imagePath, bool profilePhoto)
{
UserId = userId;
Title = title;
Description = description;
ImagePath = imagePath;
ProfilePhoto = profilePhoto;
}
public ImageParams(int id, string userId, string title, string description, string imagePath, bool profilePhoto)
{
Id = id;
UserId = userId;
Title = title;
Description = description;
ImagePath = imagePath;
ProfilePhoto = profilePhoto;
}
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[JsonProperty(PropertyName = "userId")]
public string UserId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[JsonProperty(PropertyName = "imagePath")]
public string ImagePath { get; set; }
[JsonProperty(PropertyName = "profilePhoto")]
public bool ProfilePhoto { get; set; }
public Status Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
}
public interface IImage: IEntity, IHasStatus, IDateOperationFields
{
string UserId { get; set; }
string Title { get; set; }
string Description { get; set; }
string ImagePath { get; set; }
bool ProfilePhoto { get; set; }
}
public class Image: IImage
{
public int Id { get; set; }
public string UserId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public bool ProfilePhoto { get; set; }
public Status Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public List<ReportImage> Reports { get; set; }
public List<Report> UserReports { get; set; }
}
I create the map as below;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Image, ImageParams>().ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath))).ReverseMap();
}
private static string ImagePathFormatting(string imagePath)
{
var formattedImagePath = imagePath.Contains(AppSettingsProvider.PictureBaseUrl) ? imagePath : $"{AppSettingsProvider.PictureBaseUrl}/{imagePath}";
return formattedImagePath;
}
}
I register my profile as below;
services.AddAutoMapper(typeof(MappingProfile));
And I try to map as below;
public ImageParams GetProfileImage(string userId)
{
var image = Entities.FirstOrDefault(x => x.UserId == userId && x.ProfilePhoto && x.Status == Status.Active);
return _mapper.Map<ImageParams>(image);
}
I am sure the MappingProfile is executed successfully and mapping Image object to ImageParams object as well however ImagePathFormatting function is not called.
I have tried so many variations like instead of ImagePathFormatting I have used anonymous funtion. I have also tried using IValueResolver as below;
public class CustomResolver : IValueResolver<ImageParams, Image, string>
{
public string Resolve(ImageParams source, Image destination, string imagePath, ResolutionContext context)
{
return source.ImagePath.Contains(AppSettingsProvider.PictureBaseUrl) ? source.ImagePath : $"{AppSettingsProvider.PictureBaseUrl}/{source.ImagePath}";
}
}
Whatever I try, I cannot make MapFrom nor CustomResolver invoked.
Any help would be appreciated.
First solution:
Remove from class ImageParams any constructor except default without parameters:
public class ImageParams : IImage
{
public ImageParams()
{
}
//Other members.
}
Second solution:
Add DisableConstructorMapping():
var config = new MapperConfiguration(cfg => {
cfg.AddProfile(new MappingProfile());
cfg.DisableConstructorMapping();
}
);
var mapper = config.CreateMapper();
Third solution:
CreateMap<ImageParams, Image>()
.ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath)))
.ReverseMap()
.ForMember(x => x.ImagePath, o => o.MapFrom(s => ImagePathFormatting(s.ImagePath)))
.ConstructUsing(x => new ImageParams());
Source 1
Source 2
I'd strongly suggest you use AutoMapper Execution Plan Tool tool to see exactly what automapper is doing when it runs the mapping.
Confident that will solve your problem.

MongoDB C# Driver Projection and Serialization

I have got a working code to join two mongo collections using $lookup.
[HttpGet("mongojoin")]
public IActionResult GetMongoJoinCollection([FromQuery(Name = "schoolId")] Guid schoolId)
{
var tabAssessmentCollection = mongoDatabase.GetCollection<TabAssessmentDocument>(MongoSettings.TabAssessmentDocumentName);
var tabComponentCollection =
mongoDatabase.GetCollection<TabComponentDocument>(MongoSettings.TabComponentDocumentName);
var resultMongo = tabAssessmentCollection
.Aggregate()
.Match(d => d.SchoolId.Equals(schoolId))
.Lookup(
foreignCollection: tabComponentCollection,
localField: ta => ta.ComponentId,
foreignField: tc => tc.TblId,
#as: (TabAssessmentDocument ta) => ta.Component
)
.Unwind(d => d.Component)
.ToList();
var resultList = new List<TabSubjectDocumentViewModel>();
foreach (var row in resultMongo)
{
resultList.Add(new TabSubjectDocumentViewModel()
{
Id = row["TblId"].AsGuid,
SchoolId = row["SchoolId"].AsGuid,
Name = row["Name"].AsString,
ShortName = row["ShortName"].AsString,
Active = row["Active"].AsNullableBoolean,
TabComponent = row["Component"]["Name"].AsString
});
}
return Ok(resultList);
}
}
public class TabSubjectDocumentViewModel
{
public Guid Id { get; set; }
public Guid? SchoolId { get; set; }
public string Name { get; set; }
public string ShortName { get; set; }
public bool? Active { get; set; }
public string TabComponent { get; set; }
}
public class TabAssessmentDocument
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public Guid TblId { get; set; }
public Guid? SchoolId { get; set; }
public string Name { get; set; }
public string ShortName { get; set; }
public Guid? ComponentId { get; set; }
public bool? Active { get; set; }
}
public class TabComponentDocument
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public Guid TblId { get; set; }
public Guid? SchoolId { get; set; }
public string Name { get; set; }
public string ShortName { get; set; }
public string ComponentType { get; set; }
public bool? Active { get; set; }
}
I tried using the Project method just after the .Unwind(d => d.Component), but, then I was not getting the $lookup collection data (TabComponent) in the Bsondocument. If I could Serialize this to a List, how should I be doing it ? Can anyone tell me how I should using the Projection and Serialization here ?

asp.net Core/ - Having trouble casting a linq query to a Dto

I have the following Linq statement that is currently not working.
var result = ClientDATARepository.AllIncluding(c => c.Jobs, c => c.ClientNotes, c => c.Suburb).Where(x => x.Id == id).Select(fetchedClient =>
new ClientDetailsDto {
Id = fetchedClient.Id,
ClientNo = fetchedClient.ClientNo,
Company = fetchedClient.Company,
IsWarrantyCompany = fetchedClient.IsWarrantyCompany,
CompanyName = fetchedClient.CompanyName,
ClientFirstName = fetchedClient.ClientFirstName,
ClientLastName = fetchedClient.ClientLastName,
MobilePhone = fetchedClient.MobilePhone,
DeActivated = fetchedClient.DateDeActivated.HasValue ? "true" : "false",
CreatedOn = EF.Property<DateTime>(fetchedClient, "CreatedOn").ToString("dd/MM/yyyy", CultureInfo.CurrentCulture),
ModifiedOn = EF.Property<DateTime>(fetchedClient, "ModifiedOn").ToString("dd/MM/yyyy", CultureInfo.CurrentCulture),
ClientNotes = fetchedClient.ClientNotes.Select(fetchedClientNote =>
new ClientNoteDto {
id = fetchedClientNote.Id,
Details = fetchedClientNote.Details,
}) as IQueryable<ClientNoteDto>
The initial method ClientDATARepository.AllIncluding is from the repository and is suppose to return the full details of a client:
public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
return includeProperties.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
}
Which should include all associated entities requested... which works to obtain a client however no ClientNotes are returned yet there are clientNotes in the database for the selected client.
The Dto I am trying to fill in is a follows:
public class ClientDetailsDto
{
public ClientDetailsDto()
{
}
[Key]
public int Id { get; set; }
public string ClientNo { get; set; }
public bool Company { get; set; }
public bool IsWarrantyCompany { set; get; }
public string CompanyName { get; set; }
public string ClientFirstName { get; set; }
public string ClientLastName { get; set; }
public string MobilePhone { get; set; }
public string DeActivated { get; set; }
public string CreatedOn { get; set; }
public string CreatedBy { get; set; }
public string ModifiedOn { get; set; }
public string ModifiedBy { get; set; }
public int SuburbId { get; set; }
public AddressDto Address { get; set; }
public IQueryable<ClientJobDto> ClientJobs { get; set; }
public IQueryable<ClientNoteDto> ClientNotes { get; set; }
}
ClientNoteDto is also presented for completeness:
public class ClientNoteDto
{
[Key]
public int id { get; set; }
public string Details { get; set; }
public string NoteType { get; set; }
public string CreatedOnDate { get; set; }
public string CreatedOnTime { get; set; }
public string CreatedBy { get; set; }
public string ModifiedOnDate { get; set; }
public string ModifiedOnTime { get; set; }
public string ModifiedBy { get; set; }
}
How do I get the IQueryable statement to return all the notes for this client as part of the result?

Linq nesting relationships and DTOs

I'm getting data from an Oracle database and creating an API for internal use. Some of the classes are (shortened for brevity):
CustomerOrderTab
Public partial class CustomerOrderTab
Public string OrderNo { get; set; }
[ForeignKey("CustomerMV")]
public string CustomerNo { get; set; }
public List<CustomerOrderLineTab> CustomerOrderLines { get; set; }
public List<CustomerOrderChargeTab> CustomerOrderCharge { get; set; }
public List<ShipmentOrderLineTab> ShipmentOrderLines { get; set; }
public CustomerMV CustomerMV { get; set; }
CustomerOrderDTO
Public class OrderDTO
public string OrderNo { get; set; }
public string CustomerName { get; set; }
public List<OrderLinesDTO> OrderLines { get; set; }
public List<OrderChargesDTO> OrderCharges { get; set; }
public List<ShipmentOrderLinesDTO> ShipmentOrderLines { get; set; }
ShipmentOrderLineTab
public partial class ShipmentOrderLineTab
public decimal ShipmentId { get; set; }
[ForeignKey("CustomerOrderTab")]
public string OrderNo { get; set; }
public ShipmentTab Shipment { get; set; }
ShipmentOrderLineDTO
public class ShipmentOrderLinesDTO
public decimal ShipmentId { get; set; }
public string OrderNo { get; set; }
public ShipmentTab MyShipment { get; set; }
ShipmentTab
public partial class ShipmentTab
[ForeignKey("ShipmentOrderLineTab")]
public decimal ShipmentId { get; set; }
public string ShipperAddress1 { get; set; }
public string ShipperAddress2 { get; set; }
public List<ShipmentHandlingUnitTab> ShipmentHandlingUnits { get; set; }
ShipmentDTO
public class ShipmentDTO
public decimal ShipmentId { get; set; }
public string ShipViaCode { get; set; }
public string ShipmentPayer { get; set; }
public List<ShipmentHandlingUnitDTO> ShipmentHandlingUnitDTOs { get; set; }
This Works:
var orderLines = from o in _context.CustomerOrderTab
.Where(o => o.OrderNo == orderno)
select new OrderDTO()
{
OrderNo = o.OrderNo,
CustomerName = o.CustomerMV.CustomerName,
This is a child of the order ShipmentOrderLines = o.ShipmentOrderLines
This works .Select(ob => new ShipmentOrderLinesDTO
{
ShipmentId = ob.ShipmentId,
OrderNo = ob.OrderNo,
MyShipment = ob.Shipment
Adding this does not work .Select(y => new ShipmentDTO
The error says that ShipmentTab
Doesn’t have a definition for Select {
}
}).ToList()
Shipment is a child of ShipmentOrderLine (there will only be one shipment per line)
ShipmentOrderLine is a child of CustomerOrder (there can be many lines per order)
I think the problem is that there is only one shipment per line but I've tried lots of things and can't get it to map to my DTO.
Fix your model:
ShipmentOrderLineDTO
public class ShipmentOrderLinesDTO
public decimal ShipmentId { get; set; }
public string OrderNo { get; set; }
public ShipmentDTO MyShipment { get; set; }
Select is only used to project a collection. For non-collections, you just create a new object...
var orderLines = _context.CustomerOrderTab
.Where(o => o.OrderNo == orderno)
.Select(o => new OrderDTO {
OrderNo = o.OrderNo,
CustomerName = o.CustomerMV.CustomerName,
ShipmentOrderLines = o.ShipmentOrderLines
.Select(ob => new ShipmentOrderLinesDTO {
ShipmentId = ob.ShipmentId,
OrderNo = ob.OrderNo,
MyShipment = new ShipmentDTO {
ShipmentId = ob.Shipment.ShipmentId,
...
ShipmentHandlingUnits = ob.Shipment.ShipmentHandlingUnits
.Select(shu=> new ShipmentHandlingUnitDTO {
...
}).ToList()
}
}
}).ToList()
Now with that all said, I typically have mapping extension methods on my data model classes, which allow things like this:
var orderLines = _context.CustomerOrderTab
.Where(o=>o.OrderNo == orderno)
.Select(o=> o.ToDto());
Example class:
public static class CustomerOrderTabExtensions {
public static CustomerOrderDto ToDto(this CustomerOrderTab cot) {
return new CustomerOrderDto {
OrderNo = cot.OrderNo,
...
OrderLines = cot.CustomerOrderLines
.Select(col=>col.ToDto())
.ToList()
}
}
}
Or use Automapper.

Categories