I'm following this answer AutoMapper mapping int[] or List<int> from ViewModel to a List<Type> in Domain Model and have got it binding with my CategoryId to SelectedCategoryIds on UploadSongViewModel, however I want to also get it to bind SongId to Id on UploadSongViewModel.
namespace App
{
public static class AutoMapper
{
public static MapperConfiguration Config;
public static void Initialize()
{
//It works with CategoryId, but throws an error when I include SondId.
Config = new MapperConfiguration(x =>
{
x.CreateMap<int, SongCategory>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.SongId, opt => opt.MapFrom(src => src)); //Code that's throwing exception
x.CreateMap<UploadSongViewModel, Song>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.AudioName, opt => opt.MapFrom(src => src.SongName))
.ForMember(dest => dest.AudioPath, opt => opt.MapFrom(src => src.SongPath))
.ForMember(dest => dest.SongCategories, opt => opt.MapFrom(src => src.SelectedCategoryIds))
.ForMember(dest => dest.SongCategories, opt => opt.MapFrom(src => src.Id)); //Code that's throwing exception
});
//Throws exception here because it can't bind SongId property
Config.AssertConfigurationIsValid();
}
}
}
public abstract class Entity
{
public int Id { get; set; }
}
public class SongCategory : Entity
{
public int SongId { get; set; }
public int CategoryId { get; set; }
}
public class Song : Audio
{
public string AlbumName { get; set; }
public string ArtistName { get; set; }
public List<SongCategory> SongCategories { get; set;}
}
public class UploadSongViewModel
{
public int Id { get; set; }
public string ArtistName { get; set; }
public string AlbumName { get; set; }
public int[] SelectedCategoryIds { get; set; }
public MultiSelectList CategoriesSelectList { get; set; }
}
I don't really understand what the auto mapper code is doing from Darin Dimitrov answer so it's hard to debug. If someone could explain how this mapping works for categoryId and why it is not working for SongId that would be great.
The exception I get is:
The following property on System.Collections.Generic.List1[[SoundVast.Models.SongCategory, SoundVast, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] cannot be mapped:
SongCategories
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type System.Collections.Generic.List1[[SoundVast.Models.SongCategory, SoundVast, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
Context:
Mapping to property SongCategories from System.Int32 to System.Collections.Generic.List`1[[SoundVast.Models.SongCategory, SoundVast, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Mapping from type SoundVast.Areas.Upload.Models.UploadSongViewModel to SoundVast.Models.Song
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
This can be easily achieved with some custom mapping once the basic rules have been applied:
x.CreateMap<int, SongCategory>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src));
x.CreateMap<UploadSongViewModel, Song>()
.IgnoreAllNonExisting()
.ForMember(dest => dest.AudioName, opt => opt.MapFrom(src => src.SongName))
.ForMember(dest => dest.AudioPath, opt => opt.MapFrom(src => src.SongPath))
.ForMember(dest => dest.SongCategories, opt => opt.MapFrom(src => src.SelectedCategoryIds))
.AfterMap((viewModel, model) =>
{
foreach (var item in model.SongCategories)
{
item.Id = viewModel.Id;
}
});
Related
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/
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
I'm having this exception when I'm trying to map from one object to another.
On my global.asax.cs I got this:
RoleManager<IdentityRole> roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new AppContext()));
Mapper.Initialize(cfg =>
{
cfg.CreateMap<AppUser, TokenAuthorizationModel>()
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.UserName))
.ForMember(dest => dest.Role, opt => opt.MapFrom(src => roleManager.FindById(src.Roles.First().RoleId).Name));
});
And I got this AutoMapper.AutoMapperMappingException exception on my login controller, especifically on this line:
TokenAuthorizationModel tokenClaims = Mapper.Map<TokenAuthorizationModel>(validUser);
And these are my models:
public class AppUser : IdentityUser
{
public virtual List<CourseModel> Courses { get; set; }
public string FullName { get; set; }
public int Reputation { get; set; }
}
And destination:
public class TokenAuthorizationModel
{
public string UserName { get; set; }
public string Role { get; set; }
}
Can anybody give me hand? Thanks in advance! :)
I think the problem is at this part:
.ForMember(dest => dest.Role, opt => opt.MapFrom(src => roleManager.FindById(src.Roles.First().RoleId).Name));
I would not use MapFrom in this case. Try to use ResolveUsing method instead which takes a lambda function.
.ForMember(dest => dest.Role, opt => opt.ResolveUsing(src => roleManager.FindById(src.Roles.First().RoleId).Name));
If this does not work please let me know!
You should write a resolver for the Role property and use dependency injection inside it so you have the right DbContext as you would anywhere else in your app.
I have a problem like this. But this answer it didn't work for me.
I am working on a little project. I have two domain model (Post, Source):
public class Post
{
public Post()
{
Sources = new HashSet<Source>();
}
public int Id { get; set; }
public string Title { get; set; }
public string About { get; set; }
public bool IsPublished { get; set; }
public bool IsFinished { get; set; }
public DateTime? CreatedOn { get; set; }
public int? CreatedBy { get; set; }
public virtual ICollection<Source> Sources { get; set; }
}
public class PostViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public string About { get; set; }
public bool IsPublished { get; set; }
public bool IsFinished { get; set; }
public DateTime? CreatedOn { get; set; }
public int? CreatedBy { get; set; }
public virtual ICollection<SourceViewModel> Sources { get; set; }
}
public ActionResult Edit(int id)
{
Mapper.CreateMap<Post, PostViewModel>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.About, opt => opt.MapFrom(src => src.About))
.ForMember(dest => dest.IsPublished, opt => opt.MapFrom(src => src.IsPublished))
.ForMember(dest => dest.IsFinished, opt => opt.MapFrom(src => src.IsFinished))
.ForMember(dest => dest.CreatedOn, opt => opt.MapFrom(src => src.CreatedOn))
.ForMember(dest => dest.CreatedBy, opt => opt.MapFrom(src => src.CreatedBy))
.ForMember(dest => dest.Sources, opt => opt.MapFrom(src => src.Sources))
.ReverseMap();
var Post = _PostService.Get(id);
var model = Mapper.Map<Post, PostViewModel>(Post);
return View(model);
}
But I am getting an error like that:
An exception of type 'AutoMapper.AutoMapperMappingException' occurred in AutoMapper.dll but was not handled in user code
Source domain object and SourceViewModel similar Post and PostViewModel.
Hopefully this explains my problem. How can I resolve to this problem?
In my experience the most likely problem is in your sources collection.
When you want to use the automapper best practice is to explicitly define and register a map between your two entities, Also, make sure you have defined a map between the entity and the view model of the sub collection of sources.
I created a new AutoMapperConfiguration for my mappings and I mapped all domain and view models.
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(map =>
{
map.AddProfile<MappingProfile>();
});
}
}
public class MappingProfile : Profile
{
public override string ProfileName
{
get { return "MappingProfile"; }
}
protected override void Configure()
{
Mapper.CreateMap<Post, PostViewModel>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.About, opt => opt.MapFrom(src => src.About))
.ForMember(dest => dest.IsPublished, opt => opt.MapFrom(src => src.IsPublished))
.ForMember(dest => dest.IsFinished, opt => opt.MapFrom(src => src.IsFinished))
.ForMember(dest => dest.CreatedOn, opt => opt.MapFrom(src => src.CreatedOn))
.ForMember(dest => dest.CreatedBy, opt => opt.MapFrom(src => src.CreatedBy))
.ForMember(dest => dest.Sources, opt => opt.MapFrom(src => src.Sources))
.ReverseMap();
// The other models like above...
}
}
And I registered to this configuration on global.asax:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
DependencyResolver.SetResolver(new UnityDependencyResolver(Bootstrapper.Initialise()));
//Configure AutoMapper
AutoMapperConfiguration.Configure();
}
}
Now, the process takes too long on here:
public ActionResult Edit(int id)
{
var series = _seriesService.Get(id);
var model = Mapper.Map<Series, SeriesViewModel>(series); // here
return View(model);
}
I have waited almost 10 minutes... What is the problem? :|
Mapper.CreateMap<Post, PostViewModel>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.About, opt => opt.MapFrom(src => src.About))
.ForMember(dest => dest.IsPublished, opt => opt.MapFrom(src => src.IsPublished))
.ForMember(dest => dest.IsFinished, opt => opt.MapFrom(src => src.IsFinished))
.ForMember(dest => dest.CreatedOn, opt => opt.MapFrom(src => src.CreatedOn))
.ForMember(dest => dest.CreatedBy, opt => opt.MapFrom(src => src.CreatedBy))
.ForMember(dest => dest.Sources, **opt => opt.Ignore()**)
.ReverseMap();
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());