Automapper - mapping InnerDestination to OuterSource - c#

I've followed the nested example from the Automapper Wiki but I'm having some trouble extending it. In the code below, I'm trying to map InnerDest2 to OuterSource. Specifically, I want InnerDest2.Value to be populated with OuterSource.Value but when I run, InnerDest2 comes back as null. I'm sure there is something simple I'm missing, but I just can't figure it out.
Any ideas?
namespace AutomapNestTest
{
class Program
{
static void Main(string[] args)
{
ConfigureAutomapper();
var source = new OuterSource
{
Value = 5,
Inner = new InnerSource { OtherValue = 15 },
};
var dest = new OuterDest();
AutoMapper.Mapper.Map(source, dest);
}
private static void ConfigureAutomapper()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<OuterSource, OuterDest>();
cfg.CreateMap<InnerSource, InnerDest>();
cfg.CreateMap<OuterSource, InnerDest2>();
});
}
}
public class OuterSource
{
public int Value { get; set; }
public InnerSource Inner { get; set; }
}
public class InnerSource
{
public int OtherValue { get; set; }
}
public class OuterDest
{
public int Value { get; set; }
public InnerDest Inner { get; set; }
public InnerDest2 Inner3 { get; set; }
}
public class InnerDest
{
public int OtherValue { get; set; }
}
public class InnerDest2
{
public int Value { get; set; }
}
}

You are mapping an OuterSource object to an OuterDesc object. There is a mapping configuration from OuterSource to InnerDest2 but there is no property of type OuterSource inOuterSource itself so there is no way for the propertyInner3 in OuterDest to be mapped from anything.
If you need the source.Value to be mapped to dest.Inner3.Value, you would have to do another mapping explicitly. First declare the variable:
var innerDest2 = new InnerDest2();
After this, do the mapping and set the dest.Inner3:
AutoMapper.Mapper.Map(source, innerDest2);
dest.Inner3 = innerDest2;

Related

Why AutoMapper nested mapping works without CreateMap for inner type

Model to AutoMap:
public class OuterSource
{
public int Value { get; set; }
public InnerSource Inner { get; set; }
}
public class InnerSource
{
public int OtherValue { get; set; }
}
public class OuterDest
{
public int Value { get; set; }
public InnerDest Inner { get; set; }
}
public class InnerDest
{
public int OtherValue { get; set; }
}
I found that if I comment out CreateMap for inner type.
Nested mapping still work. I would like to understand why it works, as it is not clearly stated in the documentation.
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<OuterSource, OuterDest>();
// cfg.CreateMap<InnerSource, InnerDest>(); // <-- don't need this
});
config.AssertConfigurationIsValid();
var source = new OuterSource
{
Value = 5,
Inner = new InnerSource {OtherValue = 15}
};
var mapper = config.CreateMapper();
var dest = mapper.Map<OuterSource, OuterDest>(source);
Note: I use AutoMapper version 8.1.0
Looking at source code on github for Automapper we can easily see that it gets a mapper function from a ConcurrentDictionary by using GetOrAdd method. It means that if it doesnt exist in the cache it is created.
public Delegate GetMapperFunc(MapRequest mapRequest) => _mapPlanCache.GetOrAdd(mapRequest).Typed;

AutoMapper - map to derived objects depend on condition

I want to map source class to derived (from abstract) destination classes depend on value of some property.
I have the following source classes:
public partial class ApplicationDriver
{
public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }
}
public partial class ApplicationDriverEquipment
{
public int Id { get; set; }
[StringLength(256)]
public string Make { get; set; }
[StringLength(256)]
public string Model { get; set; }
[StringLength(256)]
public string Year { get; set; }
[StringLength(256)]
public string VINNumber { get; set; }
[StringLength(256)]
public string PlateNumber { get; set; }
[StringLength(256)]
public string CurrentMileage { get; set; }
[StringLength(256)]
public string Length { get; set; }
public string Type { get; set; }
public int DriverId { get; set; }
public virtual ApplicationDriver Driver { get; set; }
}
I want to map to the following classes, depend on Type parameter:
public class ApplicationDriverDomain
{
public List<ApplicationDriverEquipmentAbstractDomain> Equipments { get; set; }
}
public abstract class ApplicationDriverEquipmentAbstractDomain
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string PlateNumber { get; set; }
public string CurrentMileage { get; set; }
public string Type { get; protected set; }
}
public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTractorDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
}
public string VINNumber { get; set; }
}
public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTrailerDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
}
public string Length { get; set; }
}
public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentStraightTruckDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentCargoVanDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
I try to do it:
ApplicationDriverEquipmentAbstractDomain GetEquipment(Infrastructure.Asset.ApplicationDriverEquipment infrastructure)
{
ApplicationDriverEquipmentAbstractDomain result = null;
var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperApplicationModel>());
var mapper = config.CreateMapper();
switch (infrastructure.Type)
{
case ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor:
result = mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer:
result = mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck:
result = mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
break;
case ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan:
result = mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
break;
}
return result;
}
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>();
CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentAbstractDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>()
.Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>()
.ForMember(dest => dest.Type, opt => opt.ResolveUsing(GetEquipment))
;
CreateMap<Infrastructure.Asset.ApplicationDriver, ApplicationDriverDomain>()
.ForMember(dest => dest.Equipments, opt => opt.MapFrom(src => src.Equipments));
but I got an error:
"Error mapping types.\r\n\r\nMapping types:\r\nApplicationDriver ->
ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver ->
Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nType Map
configuration:\r\nApplicationDriver ->
ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver ->
Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nProperty:\r\nEquipments"
Updated:
So I believe I understand what you are trying to do, and apologies I may have slightly led you down the incorrect route. You flow is basically to distinguish what infrastructure type the source object is and then create that type of object. Also you need to understand the two different Mapper set up ways.
In the first part of your code you are trying to set it up with an instance of the Mapper but then using my Static style of using the Mapper.Map I would recommend always using the static style so that you have the ability to do some more dynamic ways of pulling mapping profiles in.
Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var domain = Mapper.Map<Domain.ApplicationDriverEquipmentTractorDomain>(inf);
Next you only need to reference that a mapping type from the underlying source to the domain types in your profile i.e.
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTractorDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentCargoVanDomain>();
Then what you need to do is to call your GetEquipment method from the mapping that describes the ApplicationDriver i.e.
CreateMap<ApplicationDriver, ApplicationDriverDomain>()
.ForMember(dest => dest.Equipments, opt => opt.ResolveUsing(x => x.Equipments.Select(GetEquipment)));
private ApplicationDriverEquipmentAbstractDomain GetEquipment(ApplicationDriverEquipmentInfrastructure infrastructure)
{
switch (infrastructure.Type)
{
case "Tractor":
return Mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
case "Trailer":
return Mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
case "StraightTruck":
return Mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
case "CargoVan":
return Mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
}
return null;
}
Example Usage:
Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var inf = new ApplicationDriverEquipmentInfrastructure()
{
CurrentMileage = "mil",
Length = "123",
Make = "ccc",
Model = "15",
Type = "Tractor",
VINNumber = "vin"
};
var driver = new ApplicationDriver()
{
Equipments = new List<ApplicationDriverEquipmentInfrastructure>() {inf}
};
var domain = Mapper.Map<ApplicationDriverDomain>(driver);
Inheritance in AM works by checking the type of the source, not by using a discriminator. That's what you were supposed to understand from the docs. One way to solve your problem is to pass an existing destination to Map. Created by smth like the GetEquipment method you have there. ApplyBaseMapping is a hack, you use Include/IncludeBase to reuse configuration. Unfortunately you've also hit a bug already fixed in the MyGet build, so the real error was kind of hidden from you. The only way to debug this in your version is by checking the execution plan.

Match names in list with elements in class

I wonder if there's any way to match the names in a list with the elements in a class:
I have a class:
public class exampleClass
{
public string name { get; set; }
public string value { get; set; }
}
and a List: List<exampleClass> EnfSist
So that's the way the list is made. Now I would like to know how to match or identify the string inside "name" from my list. To match this class:
tbl_sistematicas b = new tbl_sistematicas
{
ap_enf_id_enfermedad = Convert.ToInt32(EnfSist[0].value),
ap_pac_inicio = Convert.ToInt32(EnfSist[1].value),
ap_pac_inicio_periodo = Convert.ToInt32(2].value),
ap_pac_duracion = Convert.ToInt32(EnfSist[3].value),
ap_pac_duracion_periodo = Convert.ToInt32(EnfSist[4].value),
ap_pac_tratamiento = EnfSist[5].value
};
Once being able to match the same names I won't have to specify each index of every element in the list. The elements in the list have the same name as in the table. Not all elements of the class are being used.
I have something like this: tbl_sistematicas bh = EnfSist.FindAll(x => x.name == bh.?????? );
If I understand the question, you can do this using something like automapper or ValueInjector
An example using ValueInjector
void Main()
{
List<exampleClass> EnfSist = new List<exampleClass>();
EnfSist.Add(new exampleClass { name = "ap_enf_id_enfermedad", value = "12" });
EnfSist.Add(new exampleClass { name = "apap_pac_inicio" , value = "34" });
// etc
tbl_sistematicas b = new tbl_sistematicas();
b.InjectFrom<MyInjection>(EnfSist);
}
public class MyInjection : KnownSourceValueInjection<List<exampleClass>>
{
protected override void Inject(List<exampleClass> source, object target)
{
foreach(var entry in source)
{
var property = target.GetProps().GetByName(entry.name, true);
if (property != null)
property.SetValue(target, Convert.ChangeType(entry.value, property.PropertyType));
}
}
}
public class exampleClass
{
public string name { get; set; }
public string value { get; set; }
}
public class tbl_sistematicas
{
public int ap_enf_id_enfermedad { get; set; }
public int apap_pac_inicio { get; set; }
public int ap_pac_inicio_periodo { get; set; }
public int ap_pac_duracion { get; set; }
public int ap_pac_duracion_periodo { get; set; }
public string ap_pac_tratamiento { get; set; }
}
Note, this will throw an exception if the value can not be converted to an int

Automapper - Missing type map configuration or unsupported mapping

Been struggling with this for a day now to no avail. I am new to Automapper and I am trying to map a EF domain object with a viewModel but I receive the following exception:
Missing type map configuration or unsupported mapping.\r\n\r\nMapping types:\r\nCatalogueDefinitionFile -> CatalogueDefinitionFileViewModel\r\nDigital.PriceBuilder.Core.Domain.CatalogueDefinitionFile -> Digital.PriceBuilder.Web.Models.CatalogueDefinitionFileViewModel"}
The domain POCO for CatalogueDefinitionFile is:
public class CatalogueDefinitionFile : BaseEntity
{
public CatalogueDefinitionFile()
{
this.ProductDefinitions = new List<ProductDefinition>();
}
public string TargetApplication { get; set; }
public virtual IList<ProductDefinition> ProductDefinitions { get; set; }
}
Base entity:
public abstract class BaseEntity
{
public BaseEntity()
{
this.CreatedDate = DateTime.Now;
this.UpdatedDate = DateTime.Now;
this.IsActive = true;
}
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
}
I've created a Profile:
public class DomainToViewModelMappingProfile : Profile
{
public override string ProfileName
{
get
{
return "DomainToViewModelMappings";
}
}
public DomainToViewModelMappingProfile()
{
ConfigureMappings();
}
/// <summary>
/// Creates a mapping between source (Domain) and destination (ViewModel)
/// </summary>
private void ConfigureMappings()
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<ProductDefinition, ProductDefinitionViewModel>().ReverseMap();
cfg.CreateMap<CatalogueDefinitionFile, CatalogueDefinitionFileViewModel>().ReverseMap();
});
IMapper mapper = config.CreateMapper();
mapper.Map<ProductDefinition, ProductDefinitionViewModel>(new ProductDefinition());
mapper.Map<CatalogueDefinitionFile, CatalogueDefinitionFileViewModel>(new CatalogueDefinitionFile());
}
}
The Profile is reference within a AutoMapperConfiguration class which is then referenced in Global.asax:
public class AutoMapperConfiguration
{
public static void Configure()
{
// Create Automapper profiles
Mapper.Initialize(m =>
{
m.AddProfile<DomainToViewModelMappingProfile>();
m.AddProfile<ViewModelToDomainMappingProfile>();
});
}
}
The viewModel looks like this:
public class CatalogueDefinitionFileViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string TargetApplication { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
public virtual IList<ProductDefinition> ProductDefinitions { get; set; }
}
Then in my controller I have this:
public ActionResult Index()
{
IEnumerable<CatalogueDefinitionFileViewModel> viewModel = null;
IEnumerable<CatalogueDefinitionFile> files;
files = _catalogueDefinitionFileService.GetCatalogueDefinitionFiles();
viewModel = Mapper.Map<IEnumerable<CatalogueDefinitionFile>, IEnumerable<CatalogueDefinitionFileViewModel>>(files);
return View(viewModel);
}
The exception is thrown on
viewModel = Mapper.Map<IEnumerable<CatalogueDefinitionFile>, IEnumerable<CatalogueDefinitionFileViewModel>>(files);
Can someone help me understand why this is happening please?
Thanks in advance.
Your profile doesn't do anything:
public class DomainToViewModelMappingProfile : Profile
{
// etc ...
private void ConfigureMappings()
{
// You are just creating a local mapper config/instance here and then discarding it when it goes out of scope...
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<ProductDefinition, ProductDefinitionViewModel>().ReverseMap();
cfg.CreateMap<CatalogueDefinitionFile, CatalogueDefinitionFileViewModel>().ReverseMap();
});
// I assume this is just test code
IMapper mapper = config.CreateMapper();
mapper.Map<ProductDefinition, ProductDefinitionViewModel>(new ProductDefinition());
mapper.Map<CatalogueDefinitionFile, CatalogueDefinitionFileViewModel>(new CatalogueDefinitionFile());
}
}
Try this:
public class DomainToViewModelMappingProfile : Profile
{
public override string ProfileName
{
get
{
return "DomainToViewModelMappings";
}
}
public DomainToViewModelMappingProfile()
{
ConfigureMappings();
}
/// <summary>
/// Creates a mapping between source (Domain) and destination (ViewModel)
/// </summary>
private void ConfigureMappings()
{
CreateMap<ProductDefinition, ProductDefinitionViewModel>().ReverseMap();
CreateMap<CatalogueDefinitionFile, CatalogueDefinitionFileViewModel>().ReverseMap();
}
}
The Profile type your are inheriting probably relates to a map configuration object (hence having similar/same local methods).
Disclaimer: I've not used Automapper for a while, but the above appears to be your issue.
I just tested and things work fine. The following mapping passes:
Mapper.CreateMap<CatalogueDefinitionFile, CatalogueDefinitionFileViewModel>();
var obj = Mapper.Map<IEnumerable<CatalogueDefinitionFileViewModel>>(new List<CatalogueDefinitionFile>{
new CatalogueDefinitionFile
{
Id = 101,
Name = "test",
TargetApplication = "test",
IsActive = false,
CreatedBy = "test",
CreatedDate = DateTime.Now,
UpdatedBy = "test",
UpdatedDate = DateTime.Now,
ProductDefinitions = new List<ProductDefinition> { new ProductDefinition { MyProperty = 100 } }}
});

AutoMapper - Map a complex object using naming conventions without providing inner mappings

I have two complex objects that have exactly the same properties.
Now with AutoMapper, I usually will need to do something like this:
Mapper.CreateMap<ObjA, ObjB>();
But if one of the objects has a complex inner object, Ill need to map it too:
Mapper.CreateMap<ObjAInner, ObjBInner>();
Now I have a large object that expands all the time.
I don't want to maintain the CreateMap calls each time I add a complex inner object to the main object.
Now I can probably solve this with a recursive reflection on the main object and map all the types but I'd prefer not to do that.
Is there anyway I can tell AutoMapper to Create a full recursive map(also for inner types) on a complex object?
Something like this:
Mapper.CreateFullMapOnIdenticalObjectsWithInnerTypes<ObjA, ObjB>();
Edit:
Here's an example:
two identical classes with a different namespace:
namespace NameSpace1
{
public class Obj1
{
public string Str { get; set; }
public int Num { get; set; }
public InnerObj1 InnerObj1 { get; set; }
}
public class InnerObj1
{
public string StrInner1 { get; set; }
public int NumInner1 { get; set; }
public AnotherInnerObj1 AnotherInnerObj1 { get; set; }
}
public class AnotherInnerObj1
{
public string Str { get; set; }
}
}
namespace NameSpace2
{
public class Obj1
{
public string Str { get; set; }
public int Num { get; set; }
public InnerObj1 InnerObj1 { get; set; }
}
public class InnerObj1
{
public string StrInner1 { get; set; }
public int NumInner1 { get; set; }
public AnotherInnerObj1 AnotherInnerObj1 { get; set; }
}
public class AnotherInnerObj1
{
public string Str { get; set; }
}
}
Create a mock NameSpace1.Obj1:
public static NameSpace1.Obj1 Create()
{
return new NameSpace1.Obj1
{
Num = 10,
Str = "Obj1",
InnerObj1 = new NameSpace1.InnerObj1
{
NumInner1 = 11,
StrInner1 = "InnerObj1",
AnotherInnerObj1 = new NameSpace1.AnotherInnerObj1
{
Str = "AnotherInnerObj1"
}
}
};
}
This is what I would like:
Mapper.CreateMap<NameSpace1.Obj1, NameSpace2.Obj1>();
var obj1 = Create();
var obj2 = Mapper.Map<NameSpace2.Obj1>(obj1);
But this will throw an exception since I need to define inner mappings. This will work:
Mapper.CreateMap<NameSpace1.Obj1, NameSpace2.Obj1>();
Mapper.CreateMap<NameSpace1.InnerObj1, NameSpace2.InnerObj1>();
Mapper.CreateMap<NameSpace1.AnotherInnerObj1, NameSpace2.AnotherInnerObj1>();
var obj1 = Create();
var obj2 = Mapper.Map<NameSpace2.Obj1>(obj1);

Categories