I have used auto-mapper to map single class object which is working fine, say example
class_source{
string a;
int b
}
class_destination
{
string a;
int b
}
now what I have viewModel based on multiple class object as
Source
public class WebSyncSummaryEntity
{
public Web_AppFormsEntity AppForms { get; set; }
public Web_EBS_SyncEntity EBS_Sync { get; set; }
public Web_SyncAuditLogEntity SyncAuditLog { get; set; }
}
Destination
[DataContract]
public class WebSyncSummaryView
{
[DataMember]
public Web_AppForms AppForms { get; set; }
[DataMember]
public Web_EBS_Sync EBS_Sync { get; set; }
[DataMember]
public Web_SyncAuditLog SyncAuditLog { get; set; }
}
how I can map to equivalent class object
I have tried in following code to map but error
public IList<WebSyncSummaryView> GetWebSyncSummary()
{
IList<WebSyncSummaryView> _WebSyncSummaryView = null;
IList<WebSyncSummaryEntity> _WebSyncSummaryEntity = _WebSyncCoreObject.GetWebSyncSummary();
if (_WebSyncSummaryEntity != null)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<WebSyncSummaryEntity, WebSyncSummaryView>();
});
IMapper mapper = config.CreateMapper();
_WebSyncSummaryView = mapper.Map<IList<WebSyncSummaryEntity>, IList<WebSyncSummaryView>>(_WebSyncSummaryEntity);
}
return _WebSyncSummaryView;
}
Error
"Error mapping types.\r\n\r\nMapping types:\r\nWebSyncSummaryEntity -> WebSyncSummaryView\r\nApp.Entities.WebSyncSummaryEntity -> App.WebServices.DataContract.WebSyncSummaryView\r\n\r\nType Map configuration:\r\nWebSyncSummaryEntity -> WebSyncSummaryView\r\nApp.Entities.WebSyncSummaryEntity -> App.WebServices.DataContract.WebSyncSummaryView\r\n\r\nProperty:\r\nSyncAuditLog"}
Error mapping types.
Mapping types:
IList`1 -> IList`1
System.Collections.Generic.IList`1[[App.Entities.WebSyncSummaryEntity, App.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> System.Collections.Generic.IList`1[[App.WebServices.DataContract.WebSyncSummaryView, App.Services.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Change the Line
_WebSyncSummaryView = mapper.Map<IList<WebSyncSummaryEntity>, IList<WebSyncSummaryView>>(_WebSyncSummaryEntity);
To
_WebSyncSummaryView = mapper.Map<IList<WebSyncSummaryView>>(_WebSyncSummaryEntity);
There is no need to tell AutoMapper the source type. You try to map the IList<>, but you only want to map WebSyncSummaryEntity. Just tell the Mapper what for an result you want and the rest comes from the voodoo of AutoMapper ;)
found answer
public IList<WebSyncSummaryView> GetWebSyncSummary()
{
IList<WebSyncSummaryView> _WebSyncSummaryView = null;
IList<WebSyncSummaryEntity> _WebSyncSummaryEntity = _WebSyncCoreObject.GetWebSyncSummary();
if (_WebSyncSummaryEntity != null)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true; //............... missing this
cfg.CreateMap<WebSyncSummaryEntity, WebSyncSummaryView>();
});
IMapper mapper = config.CreateMapper();
_WebSyncSummaryView = mapper.Map<IList<WebSyncSummaryEntity>, IList<WebSyncSummaryView>>(_WebSyncSummaryEntity);
}
Related
I am trying to use AutoMapper to map a DTO to an Entity class but I keep getting an error.
Here is the DTO Class:
public class Product
{
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public PriceTiers PriceTiers { get; set; }
}
and here is the Entity:
public partial class Product
{
public Product()
{
PriceTiers = new List<PriceTiers>();
}
[Key]
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public virtual ICollection<PriceTiers> PriceTiers { get; set; }
}
Why do I keep getting the following error?
{"Missing type map configuration or unsupported
mapping.\r\n\r\nMapping types:\r\nPriceTiers ->
ICollection1\r\nWeb.Areas.DEAR.DTOs.PriceTiers -> System.Collections.Generic.ICollection1[[Web.Areas.DEAR.Data.PriceTiers,
Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]\r\n\r\n
Destination Member:\r\nPriceTiers\r\n"}
This is what I have in the Profile class:
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
and this is what I use to map the classes:
var products = _mapper.Map<IEnumerable<Product>>(result.Products);
This is what is in the Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
The exception message is quite clear, the AutoMapper doesn't know how to map the data from DTOs.PriceTiers to ICollection<Data.PriceTiers>.
Solution 1: Map from DTOs.PriceTiers to ICollection<Data.PriceTiers>
I believe that Custom Type Converters is what you need.
Create Custom Type Converters.
public class ICollectionDataPriceTiersTypeConverter : ITypeConverter<DTOs.PriceTiers, ICollection<Data.PriceTiers>>
{
public ICollection<Data.PriceTiers> Convert(DTOs.PriceTiers src, ICollection<Data.PriceTiers> dest, ResolutionContext context)
{
if (src == null)
return default;
var singleDest = context.Mapper.Map<Data.PriceTiers>(src);
return new List<Data.PriceTiers>
{
singleDest
};
}
}
Add to mapping profile.
CreateMap<DTOs.PriceTiers, ICollection<Data.PriceTiers>>()
.ConvertUsing<ICollectionDataPriceTiersTypeConverter>();
Demo # .NET Fiddle
Solution 2: Map from ICollection<DTOs.PriceTiers> to ICollection<Data.PriceTiers>
If the PriceTiers in DTOs.Product supports multiple items and mapping with many to many (to ICollection<Data.ProductTiers>), then consider modifying the property as the ICollection<DTOs.PriceTiers> type.
namespace DTOs
{
public class Product
{
...
public ICollection<PriceTiers> PriceTiers { get; set; }
}
}
Did you added "CreateMapper()" method after your configurations?
Try something like that.
public class MappingProfile : Profile
{
public MappingProfile {
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
}
}
After that, on your container service, inject this dependency:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
After some more research I found out that my mapping profile was not in the right order. These are the changes I made.
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
AllowNullCollections = true;
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
CreateMap<DTOs.Product, Data.Product>()
.ForMember(dto => dto.PriceTiers, opt => opt.MapFrom(x => x.PriceTiers));
}
}
Now it maps perfectly
I have 2 classes to map:
public class Note
{
public Guid UserId { get; set; }
public Guid Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime CreationDate { get; set; }
public DateTime EditDate { get; set; }
}
public class NoteDetailsVm : IMapWith<Note>
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime CreationDate { get; set; }
public DateTime EditDate { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<Note, NoteDetailsVm>();
}
}
Here is the mapping profile and the mapping interface:
public class AssemblyMappingProfile : Profile
{
public AssemblyMappingProfile(Assembly assembly) =>
ApplyMappingsFromAssembly(assembly);
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = (from t in assembly.GetExportedTypes()
where t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapWith<>))
select t).ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
public interface IMapWith<T>
{
void Mapping(Profile profile) =>
profile.CreateMap(typeof(T), GetType());
}
I use this method to handle requests and get the viewmodel:
public async Task<NoteDetailsVm> Handle(GetNoteDetailsQuery request, CancellationToken cancellationToken)
{
var entity = await _dbContext.Notes.FirstOrDefaultAsync(note => note.Id == request.Id, cancellationToken);
if (entity == null || entity.UserId != request.UserId)
{
throw new NotFoundException(nameof(Note), request.Id);
}
return _mapper.Map<NoteDetailsVm>(entity);
}
So when I run the tests, I get such error though I have the necessary mappings:
AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types:
Note -> NoteDetailsVm
Notes.Domain.Note -> Notes.Application.Notes.Queries.GetNoteDetails.NoteDetailsVm
at lambda_method259(Closure , Object , NoteDetailsVm , ResolutionContext )
at Notes.Application.Notes.Queries.GetNoteDetails.GetNoteDetailsQueryHandler.Handle(GetNoteDetailsQuery request, CancellationToken cancellationToken)
Why isn't the mapping working and how can I fix this?
You most probably don't handle initialization in your tests. Check this guide here: https://www.thecodebuzz.com/unit-test-mock-automapper-asp-net-core-imapper/
The essence of it are these lines of code:
if (_mapper == null)
{
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new SourceMappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
_mapper = mapper;
}
They make sure there is an instance of the automapper that is properly initialized with the correct profile.
Ok, I've managed to solve the problem, it had nothing to do with the code above. Just passed the wrong assembly to the profile constructor
I'm dealing with a really awful set of generated classes which have a ton of properties of type object that contain various types I want to map. The class mappings seem to work however the property references are just copied directly without mapping the referenced objects.
How can I define a map which will map the objects inside the Items property? I have a ton of objects like this so hoping I can define this fairly simply...
Example:
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TerribleType1, TerribleType1Dto>();
cfg.CreateMap<TerribleType2, TerribleType2Dto>();
cfg.CreateMap<TerribleObject, TerribleObjectDto>();
});
var mapper = config.CreateMapper();
var terribleObject = new TerribleObject
{
Items = new object[] { new TerribleType1 { PropA = "Test1" }, new TerribleType2 { PropA = "Test2" } }
};
var terribleObjectDto = mapper.Map<TerribleObjectDto>(terribleObject);
//Want a TerribleType1Dto but instead I get a TerribleType1
Console.WriteLine(terribleObjectDto.Items[0].GetType().Name);
}
}
class TerribleObject
{
// Contains some TerribleType1 and TerribleType2 objects, these don't share a common base.
public object[] Items { get; set; }
}
class TerribleObjectDto
{
//Want this to have some TerribleType1Dto and TerribleType2Dto objects.
public object[] Items { get; set; }
}
public class TerribleType1
{
public string PropA { get; set; }
}
public class TerribleType1Dto
{
public string PropA { get; set; }
}
public class TerribleType2Dto
{
public string PropA { get; set; }
}
public class TerribleType2
{
public string PropA { get; set; }
}
Based on How can I use Automapper to map an object to an unknown destination type? it is possible to get the configured destination type for a mapping when you know the source type at runtime only. With the help of MapFrom() it is possible to build this ugly mapping for the inner object type objects:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TerribleType1, TerribleType1Dto>();
cfg.CreateMap<TerribleType2, TerribleType2Dto>();
cfg.CreateMap<TerribleObject, TerribleObjectDto>()
.ForMember(t => t.Items, m => m.MapFrom((source, target, data, context) =>
{
object[] items = source.Items;
object[] targetArray = new object[items.Length];
for (int i = 0; i < items.Length; i++)
{
object fieldEntry = items[i];
Type destinationType = context.Mapper.ConfigurationProvider
.GetAllTypeMaps()
.Single(it => it.SourceType == fieldEntry.GetType())
.DestinationType;
targetArray[i] = context.Mapper.Map(fieldEntry,
fieldEntry.GetType(),
destinationType);
}
return targetArray;
}));
});
This will convert each object in the array to the configured destination type. When you run your code now:
Console.WriteLine(terribleObjectDto.Items[0].GetType().Name);
Console.WriteLine(terribleObjectDto.Items[1].GetType().Name);
you will get the following output:
TerribleType1Dto
TerribleType2Dto
I am using auto-mapper to map class object of standard data types i.e. string, int which is working fine but now I have class with multiple custom types and I am struggling to map it to identical class object.
Source
public class WebSyncSummaryEntity
{
public Web_AppFormsEntity AppForms { get; set; }
public Web_EBS_SyncEntity EBS_Sync { get; set; }
public Web_SyncAuditLogEntity SyncAuditLog { get; set; }
}
Destination
[DataContract]
public class WebSyncSummaryView
{
[DataMember]
public Web_AppForms AppForms { get; set; }
[DataMember]
public Web_EBS_Sync EBS_Sync { get; set; }
[DataMember]
public Web_SyncAuditLog SyncAuditLog { get; set; }
}
Destination Object structure expecting
Error
"Error mapping types.\r\n\r\nMapping types:\r\nWebSyncSummaryEntity -> WebSyncSummaryView\r\nApp.Entities.WebSyncSummaryEntity -> App.WebServices.DataContract.WebSyncSummaryView\r\n\r\nType Map configuration:\r\nWebSyncSummaryEntity -> WebSyncSummaryView\r\nApp.Entities.WebSyncSummaryEntity -> App.WebServices.DataContract.WebSyncSummaryView\r\n\r\nProperty:\r\nSyncAuditLog"}
.
Mapping types:
IList`1 -> IList`1
System.Collections.Generic.IList`1[[App.Entities.WebSyncSummaryEntity, App.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] -> System.Collections.Generic.IList`1[[App.WebServices.DataContract.WebSyncSummaryView, App.Services.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Mapping class where I am getting error
public IList<WebSyncSummaryView> GetWebSyncSummary()
{
IList<WebSyncSummaryView> _WebSyncSummaryView = null;
IList<WebSyncSummaryEntity> _WebSyncSummaryEntity = _WebSyncCoreObject.GetWebSyncSummary();
if (_WebSyncSummaryEntity != null)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<WebSyncSummaryEntity, WebSyncSummaryView>();
});
IMapper mapper = config.CreateMapper();
_WebSyncSummaryView = mapper.Map<IList<WebSyncSummaryEntity>, IList<WebSyncSummaryView>>(_WebSyncSummaryEntity);
}
return _WebSyncSummaryView;
}
Config like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true; //your are missing this
cfg.CreateMap<WebSyncSummaryEntity, WebSyncSummaryView>();
});
I have the following objects:
public class DomainStudent {
public long Id { get; set; }
public string AdvisorId { get; set; }
}
public class ApiStudent {
public long Id { get; set; }
public long AdvisorName { get; set; }
}
When I run the following mapping:
ApiStudent api = new ApiStudent();
api.Id = 123;
api.AdvisorName = "Homer Simpson";
DomainStudent existing = service.load(api.Id); // 123
// at this point existing.AdvisorId = 555
existing = Mapper.Map<ApiStudent, DomainStudent>(api);
// at this point existing.AdvisorId = null
How can I configure AutoMapper such that when the property AdvisorId is missing from the source object, so that it does not get overwritten to null?
You must change the Map() call to:
Mapper.Map(api, existing);
and then configure the mapping to:
Mapper.CreateMap<ApiStudent, DomainStudent>()
.ForMember(dest => dest.AdvisorId, opt => opt.Ignore());