im trying to use the AutoMapper dynamically
i have more than 20 TypeClasses that i receive from another library Project and it can be added more in a future (i dont control that)
Im using
Automapper 8.1
.Net 3.1
C# 8
in this case i have a DTO called TypeTableUpsert
public class TypeTableUpsert
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public string TypeCode { get; set; }
public string TypeDescription { get; set; }
}
and the tables which comes from the library call like ProductType, FileType
public class ProductType
{
public long ProductTypeID {get;set}
public string ProductTypeCode { get; set; }
public string ProductTypeName { get; set; }
public string ProductTypeDescription { get; set; }
}
public class FileType
{
public long FileTypeID {get;set}
public string FileTypeCode { get; set; }
public string FileTypeName { get; set; }
public string FileTypeDescription { get; set; }
}
im trying to create a Dynamic Map with Automapper
in the example below is the normal way to map a ProductType but im trying to avoid to create a lot of Mappings
var config = new MapperConfiguration(cgg => cgg.CreateMap<TypeTableUpsertModel, ProductType>().ForMember(dest =>dest.ProductTypeDescription,
opt => opt.MapFrom(src => src.TypeDescription)));
im trying do something like that but im getting an errors
// im getting the Type Correctly
Type myType = Assamblies.ExportedTypes.SingleOrDefault(types => types.Name == typeModel.TypeTableName);
// here is were im getting the error using MyType
var config = new MapperConfiguration(cgg => cgg.CreateMap<TypeTableUpsertModel, myType>()
.ForMember(dest=> dest.GetType().GetProperties().Where(p=>p.Name.Contains("TypeDescription")).FirstOrDefault(), opt => opt.MapFrom(src => src.TypeDescription));
//i Got the next Error
AutoMapper.AutoMapperConfigurationException
HResult=0x80131500
Message=Custom configuration for members is only supported for top-level individual members on a type.
Source=AutoMapper
StackTrace:
at AutoMapper.Internal.ReflectionHelper.FindProperty(LambdaExpression lambdaExpression)
at AutoMapper.Configuration.MappingExpression`2.ForMember[TMember](Expression`1 destinationMember, Action`1 memberOptions)
at StewartConnect.Admin.Profiles.TypeTableProfile..ctor() in D:\Repos\StewartRepos\StewartConnect.Admin.APIs\StewartConnect.Admin\Profiles\TypeTableProfile.cs:line 43
im already tried in different ways but i cant make it work
Related
Im in the process of trying to flatten out a json result using Automapper but I'm getting an error telling me: Missing type map configuration or unsupported mapping
In my project I have both AutoMapper & AutoMapper dependency Injection packages installed.
My Startup.cs has the following call:
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(Startup));
}
I've set up my mapping profile as follows:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<JiraTicket, JiraDto>();
}
}
The model I want to flatten and the DTO is should flatten to:
public class JiraTicket
{
[Key]
public int TicketId { get; set; }
public string id { get; set; }
public string self { get; set; }
public string key { get; set; }
public JiraTicketFields fields { get; set; }
}
public class JiraDto
{
public int Id { get; set; }
public string Creator { get; set; }
public string Description { get; set; }
public string Key { get; set; }
public string Self { get; set; }
}
Most of the DTO variables are in the `JiraTicketFields.
Finally I'm making a call to my repo with the following lines of code:
public async Task<IEnumerable<JiraDto>> GetAll()
{
var tickets = _jiraRepo.GetAll().ToList();
var result = (IEnumerable<JiraDto>)_mapper.Map<JiraDto>(tickets);
return result;
}
Its when my project hits the var result... line that the error is thrown. What is it that I'm missing in this?
You have created a Profile that tells Automapper how to convert a single JiraTicket to a single JiraDto, but I think the problem is that you are trying to map a IEnumerable<JiraTicket> to a single JiraDto, and Automapper doesn't know how to do that.
What you probably want to do (if I understand correctly) is to map a IEnumerable<JiraTicket> to a IEnumerable<JiraDto>. Then, you would have to do this:
public async Task<IEnumerable<JiraDto>> GetAll()
{
var tickets = _jiraRepo.GetAll().ToList();
var result = _mapper.Map<IEnumerable<JiraDto>>(tickets);
return result;
}
Call this to convert List of JiraTickets to IEnumerable of JiraDtos
var result = mapper.Map<IEnumerable<JiraDto>>(tickets);
Casting in AutoMapper is done via generics parameter, not via explicit casting
What is a good way is to automap an object with some properties, one of which is a JSON string, to another object where that JSON string would be represented by properties instead of JSON.
See the source and destination classes below for an example:
CustomJson will look something like this for this example but could be different for other AlertTypes: { "AlertPrice": 500.0 }
//This is the database table representation (source)
public class Alert
{
public int Id { get; set; }
public int UserId { get; set; }
public string AlertType { get; set; }
public string CustomJson { get; set; }
}
//This is the business logic model (destination)
public class UserAlert
{
public int Id { get; set; }
public int UserId { get; set; }
public string AlertType { get; set; }
//This comes from the CustomJson string in the DB
//A different AlertType may need to map to a different object with different properties
public decimal AlertPrice { get; set; }
}
Will a ValueResolver allow me to do this or will it only be able to map part of the object? If not, perhaps I should just have a Custom property which contain the custom info instead of trying to meld it into the top-level object.
CreateMap<sourceobject, destobject>()
.AfterMap((src, dest) =>
{
dest.AlertPrice = JsonConvert.DeserializeObject<T>(json).AlertPrice;
});
CreateMap<sourceobject, destobject().AfterMap((src, dest) =>
{
dest.AlertPrice = decimal.Parse(src.AlertPrice, CultureInfo.InvariantCulture);
});
Please use above code you have use Explicity Mapping
I'm trying to map nested ICollection of one of my models to existing Dto, but I'm struggling to Map it properly with AutoMapper
Models:
public class Ingredient : BaseEntity<long>
{
[MaxLength(100)]
public string Name { get; set; }
[ForeignKey("Id")]
public int CustomerId { get; set; }
public bool IsPackaging { get; set; }
public virtual ICollection<ProductIngredient> ProductIngredient { get; set; }
public virtual ICollection<IngredientComposition> IngredientComposition { get; set; }
}
Collection Model:
public class IngredientComposition : BaseEntity<int>
{
[MaxLength(20)]
public string Type { get; set; }
[MaxLength(200)]
public string Key { get; set; }
[MaxLength(200)]
public string Value { get; set; }
}
Dto:
public class IngredientDto
{
public long Id { get; set; }
public DateTime CretedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public string Name { get; set; }
public int CustomerId { get; set; }
public int UsedCount { get; set; }
public bool IsPackaging { get; set; }
public IList<Composition> Ingredients { get; set; }
}
public class Composition
{
public string Type { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
My maps looks as follows as I'm struggling to properly set "ForMemeber" method(s):
CreateMap<Ingredient, IngredientDto>();
CreateMap<IngredientDto, Ingredient>();
Any help much appropriated!
Thanks
EDIT:
This is how I'm getting data:
return await _context.Ingredients
.Where(i => i.CustomerId ==_userResolverService.GetCustomerId())
.Include(i => i.IngredientComposition)
.Select(i => _mapper.Map<Ingredient, IngredientDto>(i))
.OrderBy(i => i.Name)
.ToListAsync();
First, you must do add CreateMap<IngredientComposition, Composition>(); and after doing this you must do change your Linq Query. You can use AutoMapper.EF6
return _context.Ingredients
.Where(i => i.CustomerId ==_userResolverService.GetCustomerId())
.Include(i => i.IngredientComposition)
.ProjectToList<IngredientDto>();
after use this you donot need use Select.
note: donot forget add _mapper.ConfigurationProvider in ProjectToList
ProjectToList<IngredientDto>(_mapper.ConfigurationProvider);
if you don't set it to get this Exception:
Mapper not initialized. Call Initialize with Appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
more detail.
Update: your properties must have the same name.if you change Dto property Ingredients to IngredientComposition don't need use ForMember.
Actually this worked for me:
Query:
return await _context.Ingredients.Where(i => i.CustomerId == _userResolverService.GetCustomerId())
.Include(sx=>sx.IngredientComposition)
.ProjectTo<IngredientDto>()
.ToListAsync();
Maps:
First of All as you suggested, internal collection mapping then main objects mapping + ForMember which worked once internal objects were mapped
CreateMap<IngredientComposition, Composition>().ReverseMap();
CreateMap<Ingredient, IngredientDto>().ForMember(d => d.Ingredients, opt=>opt.MapFrom(c=>c.IngredientComposition)).ReverseMap();
Thanks for all help!
If you make a map for the child DTO, Automapper is smart enough to figure it out without the ForMember:
CreateMap<IngredientComposition , Composition>()
.ReverseMap(); //Reverse map tells AM to go both ways
CreateMap<Ingredient, IngredientDto>()
.ReverseMap();
// CreateMap<IngredientDto, Ingredient>(); ** Not needed with ReverseMap()
I am using an Automapper to map one model to second model which has some rows with the same name as the first model. I am getting this inner exception
Missing type map configuration or unsupported mapping.
Mapping types:
Bed -> Bed
Model1.Bed -> Model2.Bed
This is the condensed version of the code.
Model 1
public class Model1
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Bed> Beds { get; set; }
public IEnumerable<Bed> Beds1 { get; set; }
public string Status {get; set;}
public string Notes {get; set;}
}
public class Model2
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Bed> Beds { get; set; }
public IEnumerable<Bed> Beds1 { get; set; }
}
public class Bed
{
public Guid Id { get; set; }
public Guid TypeId { get; set; }
public string Type { get; set; }
public string Notes { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? LastUpdatedOn { get; set; }
}
Mapping :
MapperConfiguration config = null;
config = new MapperConfiguration(cfg => { cfg.CreateMap<Model1, Model2>(); });
IMapper mapper = config.CreateMapper();
var dest = mapper.Map<Model1, Model2>(update);
update is an instance of Model1. Am I missing something? Should I do something with regards to the Bed class mapping even though both reference the same method?
Also is there a way to copy data from one model to another in this scenario other than manually doing it or using Automapper?
EDIT 1:
I am sure the error is from that mapping. I removed it and all the other fields were fine and there was no exception. But I am not sure how I should do the Bed -> Bed binding.
EDIT 2:This link seems to have the same issue and they say it's a possible bug and This one seems to have the answer although I am not sure how I can adapt it to my situation.
It seems like auto mapper is having trouble determining how to map Bed. I would try the following:
config = new MapperConfiguration(cfg => {
cfg.CreateMap<Model1, Model2>();
cfg.CreateMap<Bed, Bed>();
});
Although it seems odd that would be necessary...
I have 2 tables that saved family members and the below LINQ is 100% work and got result return. I tried to map using Automapper but it's does not work , the customerViewItem does not have data and no error , could some one please advise, thanks in advance.
POCO
public class Cust_ProfileTbl
{
[Key]
public long bintAccountNo { get; set; }
public string nvarCardName { get; set; }
public string varEmail { get; set; }
public virtual ICollection<Cust_ProfileFamilyTbl> profileFamilyParents { get; set; }
public virtual ICollection<Cust_ProfileFamilyTbl> profileFamilyChildren { get; set; }
}
public class Cust_ProfileFamilyTbl
{
[Key]
public int intProfileFamily { get; set; }
public long bintAccountNo { get; set; }
public long bintAccountNoMember { get; set; }
public virtual Cust_ProfileTbl custProfileParent { get; set; }
public virtual Cust_ProfileTbl custProfileChild { get; set; }
}
In onModelCreating
modelBuilder.Entity<Cust_ProfileFamilyTbl>()
.HasRequired(m => m.custProfileParent)
.WithMany(t => t.profileFamilyParents)
.HasForeignKey(m => m.bintAccountNo)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Cust_ProfileFamilyTbl>()
.HasRequired(m => m.custProfileChild)
.WithMany(t => t.profileFamilyChildren)
.HasForeignKey(m => m.bintAccountNoMember)
.WillCascadeOnDelete(false);
ViewModels
public class Profile
{
public long bintAccountNo { get; set; }
public string varCardNo { get; set; }
public string nvarCardName { get; set; }
public string varEmail { get; set; }
public virtual ICollection<ProfileFamily> profileFamilyParents { get; set; }
public virtual ICollection<ProfileFamily> profileFamilyChildren { get; set; }
public Profile()
{
profileFamilyParents = new Collection<ProfileFamily>();
profileFamilyChildren = new Collection<ProfileFamily>();
}
}
public class ProfileFamily
{
public int intProfileFamily { get; set; }
public long bintAccountNo { get; set; }
public long bintAccountNoMember { get; set; }
public Profile custProfileParent { get; set; }
}
LINQ and AutoMapper
System.Linq.Expressions.Expression<Func<Cust_ProfileTbl, bool>> wherep = (x) => x.bintAccountNo.Equals(1);
Cust_ProfileTbl rs = (from family in context.member.Include("profileFamilyParents.custProfileChild")
.Where(wherep)
select family).Single();
Mapper.CreateMap<Cust_ProfileTbl, EFWeb.ViewModels.Profile>();
EFWeb.ViewModels.Profile customerViewItem = Mapper.Map<Cust_ProfileTbl, EFWeb.ViewModels.Profile>(rs);
First thing is you should not create a map for mapping a List<A> to a List<B>. You should just create a map from A to B--automapper knows how to map a List<A> to a List<B> if you give it a map from A to B. This means your map should be:
Mapper.CreateMap<Cust_ProfileTbl, EFWeb.ViewModels.Profile>();
Second, automapper will not know how to map your ICollection<Cust_ProfileFamilyTbl> properties in your entity to the ICollection<ProfileFamily> properties in your viewmodel. You need to provide a map for those types:
Mapper.CreateMap<Cust_ProfileFamilyTbl, EFWeb.ViewModels.ProfileFamily>();
It is always bet to initialize your maps inside of the startup entry point of the app. In an MVC project this would be in the global.asax and in a WPF application it would be the app.xaml.cs file.
If we were initializing in the global.asax, it would look something like this:
protected void Application_Startup()
{
Mapper.CreateMap<Cust_ProfileTbl, EFWeb.ViewModels.Profile>();
}
Notice we're not mapping from type to type, not from list of type to list of type, meaning we need to iteratively map.
List<EFWeb.ViewModels.Profile> customerViewItem = rs.Select(x => Mapper.Map<Cust_ProfileTbl>(x)).ToList();
Now, there's a good chance that your map will still fail, since your properties do not match on each side. If this is the case, you should use .IgnoreMember() extension methods on your CreateMap to ignore the members that cannot be mapped because they have no corresponding receiving type.