I have two models and one view model.
Model:
public User
{
public string UserName { get; set; }
public int RoleId { get; set; }
}
public Role
{
public int RoleId { get; set; }
public string Name { get; set; }
}
ViewModel:
public UserIndex
{
public string UserName { get; set; }
public string RoleName { get; set; }
}
I tried to add config:
public class Role_User_UserIndex : Profile
{
public Role_User_UserIndex()
{
CreateMap<User, UserIndex>();
CreateMap<Role, UserIndex>()
.ForMember(des => des.RoleName, opt => opt.MapFrom(src => src.Name));
CreateMap<UserIndex, UserIndex>()
.ForMember(des => des.RoleName, opt => opt.MapFrom(src => src.RoleName))
.ForAllMembers(o => o.Condition((source, destination, member) => member != null));
}
}
and controller:
var mapper = new MapperConfiguration(cfg =>cfg.AddProfile<AutoMapperConfig.Role_User_UserIndex>()).CreateMapper();
var roles = db.Roles;
var users = db.Users;
var viewMode1 = mapper.Map<List<UserIndex>>(users);
var viewMode2 = mapper.Map<List<UserIndex>>(roles);
var indexViewModel= mapper.Map(viewModel, viewMode2);
The viewMode1 has 10 users, and the viewMode2 has 3 roles.
However, the merged result indexViewModel has 10 rows data, but the RoleName is null.
If I swap viewModel with viewMode2, indexViewModel has 3 rows data and UserName is null.
I also tried to remove ".ForAllMembers(o => o.Condition((source, destination, member) => member != null));" , but still not working.
How can I map RoleName to indexViewModel?
My AutoMapper version is 9.0.0.
Thanks!
Related
I have issues on reverse mapping multiple properties back to complex objects, even with custom value resolvers.
Here are the persistence model:
public class EmailDbo
{
public int EmailId { get; set; }
public DateTime DateCreated { get; set; }
public DateTime? DateSent { get; set; }
public string SendTo { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public bool DownloadAvailable { get; set; }
public DateTime? AdminDateSent { get; set; }
public string AdminEmail { get; set; }
public string AdminSubject { get; set; }
public string AdminBody { get; set; }
public int StatusId { get; set; }
}
I have Dapper map data from database and fill in this model.
Here are the domain models I want to map back and forth with the persistence model:
public class Email
{
public string SendTo { get; private set; }
public string Subject { get; private set; }
public string Body { get; private set; }
public DateTime? DateSent { get; private set; }
public Email(string sendTo, string subject, string body, DateTime? dateSent = null)
{
// Validations
this.SendTo = sendTo;
this.Subject = subject;
this.Body = body;
this.DateSent = dateSent;
}
}
public enum EmailTaskStatus
{
Sent = 1,
Unsent = 2
}
public class EmailTask
{
public int Id { get; private set; }
public DateTime DateCreated { get; private set; }
public Email PayerEmail { get; private set; }
public Email AdminEmail { get; private set; }
public bool DownloadAvailableForAdmin { get; private set; }
public EmailTaskStatus Status { get; private set; }
public EmailTask(int emailTaskId, DateTime dateCreated, Email payerEmail, Email adminEmail,
bool downloadAvailable, EmailTaskStatus status)
{
// Validations
this.Id = emailTaskId;
this.DateCreated = dateCreated;
this.PayerEmail = payerEmail;
this.AdminEmail = adminEmail;
this.DownloadAvailableForAdmin = downloadAvailable;
this.Status = status;
}
}
I would like to use a value object called Email for both the payer and admin email. You can tell they're just stored flatten in the database/persistence model. And the payer email is required but not the admin email.
I have the mapping configured like following:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<EmailTask, EmailDbo>()
.ForMember(dest => dest.EmailId, opts => opts.MapFrom(src => src.Id))
.ForMember(dest => dest.SendTo, opts => opts.MapFrom(src => src.PayerEmail.SendTo))
.ForMember(dest => dest.Subject, opts => opts.MapFrom(src => src.PayerEmail.Subject))
.ForMember(dest => dest.Body, opts => opts.MapFrom(src => src.PayerEmail.Body))
.ForMember(dest => dest.DateSent, opts => opts.MapFrom(src => src.PayerEmail.DateSent))
.ForMember(dest => dest.DownloadAvailable, opts => opts.MapFrom(src => src.DownloadAvailableForAdmin))
.ForMember(dest => dest.AdminEmail, opts =>
{
opts.PreCondition(src => (src.AdminEmail != null));
opts.MapFrom(src => src.AdminEmail.SendTo);
})
.ForMember(dest => dest.AdminSubject, opts =>
{
opts.PreCondition(src => (src.AdminEmail != null));
opts.MapFrom(src => src.AdminEmail.Subject);
})
.ForMember(dest => dest.AdminBody, opts =>
{
opts.PreCondition(src => (src.AdminEmail != null));
opts.MapFrom(src => src.AdminEmail.Body);
})
.ForMember(dest => dest.AdminDateSent, opts =>
{
opts.PreCondition(src => (src.AdminEmail != null));
opts.MapFrom(src => src.AdminEmail.DateSent);
})
.ForMember(dest => dest.StatusId, opts => opts.MapFrom(src => (int)src.Status))
.ReverseMap()
.ForCtorParam("status", opts => opts.MapFrom(src => src.StatusId))
.ForMember(dest => dest.PayerEmail, opts => opts.MapFrom<PayerEmailValueResolver>())
.ForMember(dest => dest.AdminEmail, opts => opts.MapFrom<AdminEmailValueResolver>());
}
}
After ReverseMap(), I want to grab multiple properties and construct the complex object Email. Hence I define two custom value resolvers for that:
public class PayerEmailValueResolver : IValueResolver<EmailDbo, EmailTask, Email>
{
public Email Resolve(EmailDbo emailDbo, EmailTask emailTask, Email email, ResolutionContext context)
{
return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);
}
}
public class AdminEmailValueResolver : IValueResolver<EmailDbo, EmailTask, Email>
{
public Email Resolve(EmailDbo emailDbo, EmailTask emailTask, Email email, ResolutionContext context)
{
if (String.IsNullOrWhiteSpace(emailDbo.AdminEmail) &&
String.IsNullOrWhiteSpace(emailDbo.AdminSubject) &&
String.IsNullOrWhiteSpace(emailDbo.AdminBody) &&
!emailDbo.AdminDateSent.HasValue)
{
return null;
}
return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);
}
}
As always, the mapping from the domain model to the Dbo works fine:
but not the other way, from Dbo to domain model. It's throwing exceptions:
Unhandled exception. System.ArgumentException: Program+EmailTask needs to have a constructor with 0 args or only optional args. (Parameter 'type')
at lambda_method32(Closure , Object , EmailTask , ResolutionContext )
at AutoMapper.Mapper.MapCore[TSource,TDestination](TSource source, TDestination destination, ResolutionContext context, Type sourceType, Type destinationType, IMemberMap memberMap)
at AutoMapper.Mapper.Map[TSource,TDestination](TSource source, TDestination destination)
at AutoMapper.Mapper.Map[TDestination](Object source)
.Net Fiddle demo: https://dotnetfiddle.net/DcTsPG
I wonder if AutoMapper confuses about those two Email objects: payer email and admin email, because they're both are Email type.
In reverse map AutoMapper is failing to create an instance of EmailTask.
Add a parameterless constructor to your EmailTask class -
public EmailTask()
{
// AutoMapper use only
}
Also, since your value resolvers are creating instance of Email, add a parameterless constructor to your Email class too -
public Email()
{
// AutoMapper use only
}
Finally, modify the PayerEmail and AdminEmail properties in EmailTask class so they can be set publicly -
public Email PayerEmail { get; set; }
public Email AdminEmail { get; set; }
That should solve your issue.
EDIT :
#David Liang, after reading your comment I'd say, to suit your scenario in light of DDD, you might need to modify your current mapping approach.
The thing is, when you are mapping EmailDbo from EmailTask, the process is easier because EmailDbo is a DTO type class with no parameterized constructor. Therefore, the property mapping only is enough to do the job.
But when you are trying to map EmailTask from EmailDbo, you are trying to instantiate a domain model class which has very strictly defined parameterized constructor that takes complex types as parameters, and is trying to protect how it's properties can and cannot be accessed from outside. Therefore, the .ReverseMap() approach you are using currently will not be very helpful, because the property mapping only will not be enough to provide you all the constructor parameters needed to instantiate the class. There's also AutoMapper's naming convention in the play.
Following is a mapping configuration for EmailTask from EmailDbo, where the reverse mapping is separated out and the value resolvers are refactored out into a helper class. The forward mapping remained unchanged.
CreateMap<EmailDbo, EmailTask>()
.ConstructUsing((s, d) =>
new EmailTask(
s.EmailId,
s.DateCreated,
Helper.GetPayerEmail(s),
Helper.GetAdminEmail(s),
s.DownloadAvailable,
(EmailTaskStatus)s.StatusId))
.IgnoreAllPropertiesWithAnInaccessibleSetter();
The Helper class -
public class Helper
{
public static Email GetPayerEmail(EmailDbo emailDbo)
{
return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);
}
public static Email GetAdminEmail(EmailDbo emailDbo)
{
if (string.IsNullOrWhiteSpace(emailDbo.AdminEmail) && string.IsNullOrWhiteSpace(emailDbo.AdminSubject)
&& string.IsNullOrWhiteSpace(emailDbo.AdminBody) && !emailDbo.AdminDateSent.HasValue)
{
return null;
}
return new Email(emailDbo.SendTo, emailDbo.Subject, emailDbo.Body, emailDbo.DateSent);
}
}
Here is the complete fiddle - https://dotnetfiddle.net/2MxSdt
How to mapping IdContributors (collection of strings) to a collection (Contributors), inside a collection TAction, with a property (ContributorId) of string, using LINQ and AutoMapper ?
public ViewModelToDomainMappingProfile()
{
CreateMap<ActionViewModel, TAction>();
//.ForMember(d => d.Contributors, opt => opt.MapFrom(a => ids = a.IdContributors.Select(x => { })));
}
Models
Model TAction
public class TAction
{
public Guid Id {get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<TActionContributor> Contributors { get; set; }
}
public class TActionContributor
{
public Guid TActionId { get; set; }
[ForeignKey("TActionId")]
public TAction Action { get; set; }
public string ContributorId { get; set; }
[ForeignKey("ContributorId")]
public ApplicationUser Contributor { get; set; }
}
ActionViewModel
public class ActionViewModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<string> IdContributors { get; set; }
}
I can see two choices here, you'll choose the solution which better suits your needs:
1) Select() in from MapFrom()
CreateMap<ActionViewModel, TAction>()
.ForMember(
destination => destination.Contributors,
options => options.MapFrom((source, destination) =>
{
var contributors = source
.IdContributors
.Select(id => new TActionContributor
{
Action = destination,
Contributor = new ApplicationUser
{
Id = id,
},
ContributorId = id,
TActionId = source.Id,
})
.ToList();
contributors.ForEach(ac => ac.Contributor.Contributors = contributors);
return contributors;
}));
2) Custom ValueResolver
This is the same as the first solution, maybe more readable if you want to keep your mapping profiles clean and move the custom logic away when possible.
public class TActionContributorValueResolver : IValueResolver<ActionViewModel, TAction, ICollection<TActionContributor>>
{
public ICollection<TActionContributor> Resolve(
ActionViewModel source,
TAction destination,
ICollection<TActionContributor> destMember,
ResolutionContext context)
{
var contributors = source
.IdContributors
.Select(id => new TActionContributor
{
Action = destination,
Contributor = new ApplicationUser
{
Id = id,
},
ContributorId = id,
TActionId = source.Id,
})
.ToList();
contributors.ForEach(ac => ac.Contributor.Contributors = contributors);
return contributors;
}
}
Configuration:
CreateMap<ActionViewModel, TAction>()
.ForMember(
destination => destination.Contributors,
options => options.MapFrom<TActionContributorValueResolver>());
Final note:
contributors.ForEach(ac => ac.Contributor.Contributors = contributors);
Line above allows you to traverse endlessly from TAction through TActionContributor to ApplicationUser and the other way. If you don't need that feature, feel from to remove it and return the contributors list right away.
I am trying to learn asp.NetCore 2.2. I am trying to setup a simple one page site. I have run into a problem with Automapper where manual Mappinng using forMember() is working at a top level for CreateMap<Listing, ListingSearchResultsDto>().ForMember(ListingPhotosUrl) but not at a lower level. I have another mapping CreateMap<User, UserDetailsDto>() where user contains an object Mylistings of type Listing. Mylistings is correctly auto mapped to ListingSearchResultsDto but manual configuration CreateMap<Listing, ListingSearchResultsDto>().ForMember(ListingPhotosUrl) is not applied.
I Have tried CreateMap<User, UserDetailsDto>().Formember(dest.Mylistings.ListingPhotosUrl,src.Mylistings.Photos.Url) but it seems that is not possible.
I Also tried this-> But no luck
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDetailsDto>();
cfg.CreateMap<Listing, ListingSearchResultsDto>()
.ForMember(dest => dest.ListingPhotosUrl, opt =>
{
opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
});
});
var mapper = config.CreateMapper();
The Code:
AutoMappperProfiles
public AutoMapperProfiles()
{
CreateMap<Listing, ListingSearchResultsDto>()
.ForMember(dest => dest.ListingPhotosUrl, opt =>
{
opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
});
CreateMap<User, UserDetailsDto>();
CreateMap<ListingPhoto, ListingPhotosDetailedDto>();
}
User
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Listing> MyListings { get; set; }
}
UserDetailsDto
public class UserDetailsDto
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<ListingSearchResultsDto> MyListings { get; set;}
}
Listing
public int id { get; set; }
public string Category { get; set; }
public string Title { get; set; }
public ICollection<ListingPhoto> Photos { get; set; }
ListingSearchResultsDto
public class ListingSearchResultsDto
{
public int id { get; set; }
public string Title { get; set; }
public string ListingPhotosUrl { get; set; }
}
I am using CreateMap<Listing, ListingSearchResultsDto>().Formember(des,src) to manually map a destination property ListingPhotosUrl. I have another mapping CreateMap<User, UserDetailsDto>(). Inside User & UsedetailsDto classes i have a objects called MyListings of types ICollection<Listing> and ICollection<ListingSearchResultsDto> respectively. MyListings object is auto mapped correctly but ListingPhotosUrl manual mapping is not being applied. CreateMap<Listing,ListingSearchResultsDto>.Formember(des,src)) manual mapping is working at top level, but not at deeper level inside CreateMap<User, UserDetailsDto>(), is there anyway to fix this? thanks
FIXED - Automapper was working fine. Issue in Entity Framework DbContext. I did not include the photos as related data in the EF Core method for loading USER data GETUSER(). It was working with EF Core method for loading LISTING GetListing() because i had an include for photos Include(p => p.Photos).
After adding .ThenInclude(p => p.Photos) in GetUser(), the photos were returned with USER data and automapper successfully mapped User data and ListingPhotosUrl manual mapping was applied successfully.
Entity Framework Core DbContext:
public async Task<User> GetUser(int id)
{
var user = await _context.Users
.Include(a => a.Avatar)
.Include(l => l.MyListings)
.ThenInclude(p => p.Photos)
.FirstOrDefaultAsync(u => u.Id == id);
return user;
}
public async Task<Listing> GetListing(int id)
{
var listing = await _context.Listings
.Include(p => p.Photos)
.FirstOrDefaultAsync(l => l.id == id);
return listing;
}
Entities:
public class Entity
{
public int Id { get; set; }
}
public class User : Entity
{
public string Name { get; set; }
public Company Company { get; set; }
}
public class Company : Entity
{
public string Name { get; set; }
}
Dto's:
public class EntityDto
{
public int Id { get; set; }
}
public class UserDto : EntityDto
{
public string Name { get; set; }
public int? CompanyId { get; set; }
}
So I want to map User to UserDto like User.Company == null => UserDto.CompanyId == null and vice versa.
That is my Automapper configuration:
Mapper.Initialize(configuration =>
{
configuration
.CreateMap<User, UserDto>()
.ReverseMap();
});
This works fine:
[Fact]
public void UnattachedUserMapTest()
{
// Arrange
var user = new User { Company = null };
// Act
var userDto = Mapper.Map<User, UserDto>(user);
// Assert
userDto.CompanyId.Should().BeNull();
}
but this test fails:
[Fact]
public void UnattachedUserDtoMapTest()
{
// Arrange
var userDto = new UserDto { CompanyId = null };
// Act
var user = Mapper.Map<UserDto, User>(userDto);
// Assert
user.Company.Should().BeNull();
}
Details:
Expected object to be <null>, but found
Company
{
Id = 0
Name = <null>
}
Doesn't work for me:
...
.ReverseMap()
.ForMember(user => user.Company, opt => opt.Condition(dto => dto.CompanyId != null));
and well as that (just for example):
...
.ReverseMap()
.ForMember(user => user.Company, opt => opt.Ignore());
Why does Automapper create nested object and how can I prevent it?
That "suddenly" bit is funny :)
configuration.CreateMap<User, UserDto>().ReverseMap().ForPath(c=>c.Company.Id, o=>o.Ignore());
You have a default MapFrom with CompanyId and that is applied in reverse. For details see this and a few other similar issues.
In the next version (on MyGet at the moment) you'll also be able to use
configuration.CreateMap<User, UserDto>().ReverseMap().ForMember(c=>c.Company, o=>o.Ignore());
Using Automapper I am trying to map one object to another. One property is a class called Task containing a list of customers. The other class is called Result and contains a count of customers as well as another list of customers.
This is my current approach which fills information into the order properties correctly, but fails in result, which is still null. How can I get the List into result? How do i need to change the maps and do i need to create a map in both directions, or this this not necessary?
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CustomerPost.RootObject, Customers.RootObject>();
cfg.CreateMap<CustomerPost.Order, Customers.Order>();
cfg.CreateMap<Customers.Result, CustomerPost.Task>();
cfg.CreateMap<CustomerPost.Task, Customers.Result>()
.ForMember(x => x.customerscount, opt => opt.Ignore())
.ForMember(x => x.customerstotalcount, opt => opt.Ignore());
});
try
{
Mapper.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException ex)
{
//TODO: Handle this
throw ex;
}
var customer = Mapper.Map<CustomerPost.RootObject, Customers.RootObject>(input);
here are my current classes (Customer):
public class Result
{
public int customerstotalcount { get; set; }
public int customerscount { get; set; }
public List<Customer> customers { get; set; }
}
public class RootObject
{
public Status status { get; set; }
public Order order { get; set; }
public Result result { get; set; }
}
CustomerPost:
public class Task
{
public List<Customer> customers { get; set; }
}
public class RootObject
{
public Order order { get; set; }
public List<Task> tasks { get; set; }
}
Okay so the solution to my problem was that my mapping didn't find "result" so i've just mapped my RootObject like this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<CustomerPost.RootObject, Customers.RootObject>()
.ForMember(x => x.status, opt => opt.Ignore())
.ForMember(x => x.order, opt => opt.Ignore())
.ForMember(dest => dest.result, src => src.MapFrom(opt => opt.tasks.FirstOrDefault()));
then i went ahead and just mapped the result like this:
var result = Mapper.Map<CustomerPost.Task, Customers.Result>(input.tasks.FirstOrDefault());
var customer = new Customers.Customer();
customer = result.customers.FirstOrDefault();
and just bound it to a new Customerobject. Then all my information got transmitted as expected