Mapping viewmodel children with parameter AutoMapper - c#

I'm pretty new using AutoMapper and i run into an issue
I have a model like this.
public class StepUp {
public string Example {get;set;}
public string Example2 {get;set;}
public decimal? auxValue { get;set; }
}
But i have two ViewModels as destination
public class NuevoStepUpViewModel()
{
public bool TieneAuxiliar { get; set; }
public string Example { get;set; }
public CargaDatosElectricos CargaDatosElectricos { get; set; }
}
public class CargaDatosElectricos {
public CargaDatosElectricos(bool tieneAuxiliar)
{
TieneAuxiliar = tieneAuxiliar;
}
public readonly bool TieneAuxiliar;
public string Example2 { get; set; }
}
I think some like this:
CreateMap<StepUp,NuevoStepUpViewModel()
.ForMember(x => x.TieneAuxiliar, x => x.MapFrom(c => c.auxValue.HasValue))
.ForMember(x => x.Example, x => x.MapFrom(c => c.Example))
.ForMember(x => x.CargaDatosElectricos.Example2, x => x.MapFrom(c => c.Example2))
.BeforeMap((x,y) => {
x.CargaDatosElectricos = new CargaDatosElectricos(c.auxValue.HasValue);
});
But i'm getting
Expression 'x => x.CargaDatosElectricos.Example2' must resolve to
top-level member and not any child object's properties
How should i create my mapper configuration to do this type of mapping?

There are some errors on your code. You could configure better your mapping using the AfterMap scope instead of BeforeMap to provide a complex configuration. (I am not sure but I think the) AutoMapper will not instance a property where the type is a class. So, you have to do it on the construtor of the destination class (VIewModel) or do it on AfterMap.
The TieneAuxiliar property will not allow you to set a value when it is readonly, so, you will not able to configure a map to this property. I change it to a public classic property.
See the working sample here:
https://dotnetfiddle.net/HSyUVv
using System;
using AutoMapper;
public class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<StepUp, NuevoStepUpViewModel>()
.ForMember(vm => vm.TieneAuxiliar, opt => opt.MapFrom(e => e.auxValue.HasValue))
.ForMember(vm => vm.Example, opt => opt.MapFrom(e => e.Example))
.AfterMap((e, vm) =>
{
vm.CargaDatosElectricos.Example2 = e.Example2;
});
});
var mapper = config.CreateMapper();
var stepUp = new StepUp()
{
Example = "Example 1",
Example2 = "Example 2",
auxValue = 10m
};
var viewModel = mapper.Map<StepUp, NuevoStepUpViewModel>(stepUp);
Console.WriteLine("SteUp was converted to ViewModel");
Console.WriteLine("TieneAuxiliar: {0}", viewModel.TieneAuxiliar);
Console.WriteLine("Example: {0}", viewModel.Example);
Console.WriteLine("CargaDatosElectricos.TieneAuxiliar: {0}", viewModel.CargaDatosElectricos.TieneAuxiliar);
Console.WriteLine("CargaDatosElectricos.Exemple2: {0}", viewModel.CargaDatosElectricos.Example2);
}
public class StepUp
{
public string Example { get; set; }
public string Example2 { get; set; }
public decimal? auxValue { get; set; }
}
public class NuevoStepUpViewModel
{
public bool TieneAuxiliar { get; set; }
public string Example { get;set; }
public CargaDatosElectricos CargaDatosElectricos { get; set; }
public NuevoStepUpViewModel()
{
this.CargaDatosElectricos = new CargaDatosElectricos();
}
}
public class CargaDatosElectricos
{
public CargaDatosElectricos()
{
}
public bool TieneAuxiliar { get; set; }
public string Example2 { get; set; }
}
}

Related

Automapper include all properties from parent class

I've the following code:
using System;
using AutoMapper;
namespace AutoMapperPlayground
{
public class Program
{
public static void Main()
{
var derived = new Derived {
Title = "a",
Base = new Base {
Id = 1,
Test = "b"
}
};
Mapper.CreateMap<Derived, DerivedDTO>();
var derivedDTO = Mapper.Map<DerivedDTO>(derived);
Console.WriteLine("{0},{1},{2}", derivedDTO.Test, derivedDTO.Id, derivedDTO.Title);
}
}
public class Base
{
public int Id { get; set; }
public string Test {get; set; }
}
public class Derived
{
public Base Base {get; set; }
public string Title {get; set; }
}
public class BaseDTO
{
public int Id { get; set; }
public string Test {get; set; }
}
public class DerivedDTO : BaseDTO
{
public string Title {get; set; }
}
}
The output is ,0,a.
Would it be possible to have id and title properties populated from Base?
Sample .net fiddle
Thanks
Flattening is one of the core concepts of AutoMapper, and is done by convention; your DTO property names need to be prefixed with the property name of the composed object in your source type:
public class DerivedDTO
{
public int BaseId { get; set; }
public string BaseTest { get; set; }
public string Title { get; set; }
}
However, if you want to retain your existing structure (DerivedDTO inheriting from BaseDTO), you would need to define those mappings manually:
Mapper.CreateMap<Derived, DerivedDTO>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Base.Id)),
.ForMember(dest => dest.Test, opt => opt.MapFrom(src => src.Base.Test));
Or you could create a mapping from Base to DerivedDTO, and include that in your Derived mapping:
Mapper.CreateMap<Base, DerivedDTO>(); // Map Id and Test
Mapper.CreateMap<Derived, DerivedDTO>() // Map Title
.IncludeMembers(src => src.Base); // Reuse above mapping to include Base Id and Test

Map Entity Framework collection to comma delimted string with automapper

I have a parent class:
public class Parent
{
...
public List<Location> Locations { get; set; }
}
Location class:
public class Location
{
public int LocationId { get; set; }
public string Name { get; set; }
}
Destination class for mapping:
public class Destination
{
...
public string DelimitedLocations { get; set; }
}
I need to map the LocationId list from Locations to a comma delimited string using automapper.
Here are several things I have tried:
CreateMap<Parent, Destination>().ForMember(d => d.DelimitedLocations , o => o.MapFrom(s => string.Join(",", s.Locations.ToList().Select(t => t.LocationID.ToString()))))
Result:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable`1[System.String])' method, and this method cannot be translated into a store expression.
Next attempt:
CreateMap<Parent, Destination>()..ForMember(d => d.TestPlotLocationsSelected, o => o.MapFrom(s => s.TestPlotLocations.ToList().Select(t => string.Join(",", t.TestPlotLocationID.ToString()))))
Result:
No method 'ToString' exists on type 'System.Collections.Generic.IEnumerable`1[System.String]'.
Not sure what to try next.
Select statement should be something like
o.Locations.Select(x => x.LocationId).ToList()
Demo
public class Program
{
public static void Main()
{
Initialize();
var source = new Parent
{
Locations = new List<Location>
{
new Location {LocationId = 1, Name = "One"},
new Location {LocationId = 2, Name = "Two"},
new Location {LocationId = 3, Name = "Three"},
}
};
var destination = Mapper.Map<Parent, Destination>(source);
Console.ReadLine();
}
public static void Initialize()
{
MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Parent, Destination>()
.ForMember(dest => dest.DelimitedLocations, mo => mo.MapFrom(src =>
src.Locations != null
? string.Join(",", src.Locations.Select(x => x.LocationId).ToList())
: ""));
});
Mapper = MapperConfiguration.CreateMapper();
}
public static IMapper Mapper { get; private set; }
public static MapperConfiguration MapperConfiguration { get; private set; }
}
public class Parent
{
public List<Location> Locations { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public string Name { get; set; }
}
public class Destination
{
public string DelimitedLocations { get; set; }
}
Result

Automapper with resolver throwing "Error mapping types"

We have a class inside another class as a property which needs to be mapped using Automapper. We have written a resolver which will map the source class properties to destinationMember properties. I have written the below logic which is not working.
We are receiving the following error.
Error mapping types.
Mapping types: SubscriberDTO -> Subscriber
ConsoleAutomapperTestHarness.SubscriberDTO ->
ConsoleAutomapperTestHarness.Subscriber
Type Map configuration: SubscriberDTO -> Subscriber
ConsoleAutomapperTestHarness.SubscriberDTO ->
ConsoleAutomapperTestHarness.Subscriber
Property: SubscriberSettings
using AutoMapper; //5.1.1.0
using System;
namespace ConsoleAutomapperTestHarness
{
public class Program
{
public static void Main(string[] args)
{
SubscriberDTO subDTO = new SubscriberDTO();
subDTO.AllowAddPFA = true;
subDTO.AllowAutoPay = true; ;
subDTO.SubscriberID = 10000;
subDTO.FirstName = "Kishor";
new SubscriberAutoMapper();
Subscriber sub = Mapper.Map<SubscriberDTO, Subscriber>(subDTO);
Console.WriteLine(sub.SubscriberSettings.AllowAddPFA.ToString());
Console.ReadLine();
}
}
public class SubscriberAutoMapper
{
public SubscriberAutoMapper()
{
Mapper.Initialize(cfg => {
cfg.CreateMap<SubscriberDTO, Subscriber>()
.ForMember(dest => dest.SubscriberSettings, opt => opt.ResolveUsing<SubscriberAutoMapperResolver>());
});
Mapper.AssertConfigurationIsValid();
}
}
public class SubscriberAutoMapperResolver : IValueResolver<SubscriberDTO, Subscriber, Settings>
{
public Settings Resolve(SubscriberDTO source, Subscriber destination, Settings destMember, ResolutionContext context)
{
//line which is working.
return new Settings() { AllowAddPFA = source.AllowAddPFA };
//line which is not working
// var result = context.Mapper.Map<SubscriberDTO, Settings>(source);
// var result = Mapper.Map<SubscriberDTO, Settings>(source);
//var result = Mapper.Map<SubscriberDTO, Settings>(source,destMember);
//var result = context.Mapper.Map<SubscriberDTO, Settings>(source, destMember, context);
//return result;
}
}
public class Subscriber
{
public int SubscriberID { get; set; }
public Settings SubscriberSettings { get; set; }
public string FirstName { get; set; }
}
public class Settings
{
public bool AllowEnrollment { get; set; }
public bool AllowAutoPay { get; set; }
public bool AllowAddPFA { get; set; }
}
public class SubscriberDTO
{
public int SubscriberID { get; set; }
public string FirstName { get; set; }
public bool AllowEnrollment { get; set; }
public bool AllowAutoPay { get; set; }
public bool AllowAddPFA { get; set; }
}
}
The ValueResolver seems overkill honestly, you can drop it completely and achieve the desired result with as little as this (given that the default AutoMapper behaviour makes it redundant to explicitly specify properties when they have the same name, as in most of your models basically):
Mapper.Initialize(cfg => {
cfg.CreateMap<SubscriberDTO, Subscriber>()
.ForMember(d => d.SubscriberSettings, o => o.MapFrom(s => s));
cfg.CreateMap<SubscriberDTO, Settings>();
});

Using Automapper with a collection of abstract objects

Please take a look at this rather contrived example of what I'm trying to do.
First, the database models:
public class Report
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Worker> Workers { get; set; }
}
public abstract class Worker
{
public Guid Id { get; set; }
}
public class Fireman : Worker
{
public string Station { get; set; }
}
public class Cleaner : Worker
{
public string FavoriteSolvent { get; set; }
}
Now the view models:
public class AddReportViewModel
{
public string Name { get; set; }
public List<AddFiremanViewModel> Firemen { get; set; }
public List<AddCleanerViewModel> Cleaners { get; set; }
}
public class AddFiremanViewModel
{
public string Station { get; set; }
}
public class AddCleanerViewModel
{
public string FavoriteSolvent { get; set; }
}
And finally the Automapper profile:
public class ReportProfile : Profile
{
protected override void Configure()
{
CreateMap<AddReportViewModel, Report>();
CreateMap<AddFiremanViewModel, Fireman>();
CreateMap<AddCleanerViewModel, Cleaner>();
}
}
I want the Firemen and Cleaners to both populate the Workers collection, which is an ICollection<Worker>. I hope this makes sense. How can I do this?
Your view model doesn't contain any Id field so I set those up as Ignore in the mapping. Then, I just used LINQ's Union clause to combine the two source lists into a single collection (after converting each one using Automapper). Here's the mapping:
Mapper.CreateMap<AddReportViewModel, Report>()
.ForMember(d => d.Id, o => o.Ignore())
.ForMember(d => d.Workers, o => o.MapFrom(
s => Mapper.Map<ICollection<AddFiremanViewModel>, ICollection<Fireman>>(s.Firemen)
.Union<Worker>(Mapper.Map<ICollection<AddCleanerViewModel>, ICollection<Cleaner>>(s.Cleaners))))
;
Mapper.CreateMap<AddFiremanViewModel, Fireman>()
.ForMember(d => d.Id, o => o.Ignore())
;
Mapper.CreateMap<AddCleanerViewModel, Cleaner>()
.ForMember(d => d.Id, o => o.Ignore())
;
And here's an example of using it:
var vm = new AddReportViewModel
{
Name = "Sample",
Cleaners = new List<AddCleanerViewModel>
{
new AddCleanerViewModel {FavoriteSolvent = "Alcohol"}
},
Firemen = new List<AddFiremanViewModel>
{
new AddFiremanViewModel {Station = "51"},
new AddFiremanViewModel {Station = "49"}
}
};
var db = Mapper.Map<AddReportViewModel, Report>(vm);

How to use Automapper to map an object to an interface at a different level?

I'm new to Automapper and am trying to map the data from an object in a list to an interface in a list. Add to that the lists are in different levels of the containing objects:
// class definitions
public class MyViewModel
{
public int ViewModelId { get; set; }
public IList<ViewItem> Page1Selections { get; set; }
public IList<ViewItem> Page2Selections { get; set; }
}
public class ViewItem
{
public int ItemId { get; set; }
public bool IsSelected { get; set; }
}
public class MyDbModel
{
public int DbModelId { get; set; }
public IPageSelection Page1Selections { get; set; }
public IPageSelection Page2Selections { get; set; }
}
public interface IPageSelection
{
int PageNumber { get; set; }
IList<IMyDbItem> PageSelections { get; set; }
}
public class PageSelection : IPageSelection
{
public int PageNumber { get; set; }
public IList<IMyDbItem> PageSelections { get; set; }
}
public interface IMyDbItem
{
int ItemId { get; set; }
bool IsSelected { get; set; }
}
public class MyDbItem : IMyDbItem
{
public int ItemId { get; set; }
public bool IsSelected { get; set; }
}
// mapping code
MyViewModel myVm = new MyViewModel();
myVm.ViewModelId = 123;
myVm.Page1Selections = new List<ViewItem>();
myVm.Page1Selections.Add(new ViewItem() { ItemId = 1, IsSelected = true });
myVm.Page2Selections = new List<ViewItem>();
myVm.Page2Selections.Add(new ViewItem() { ItemId = 2, IsSelected = true });
Mapper.Initialize(cfg =>
{
cfg.BindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
cfg.CreateMap<MyViewModel, MyDbModel>();
cfg.CreateMap<ViewItem, IMyDbItem>();
});
MyDbModel myDb = new MyDbModel();
myDb = Mapper.Map<MyDbModel>(myVm); //<== exception here
Sorry for the long attachment. But I'm fighting both an object to interface and hierarchical mismatch mapping problem.
Here's one way you could accomplish this:
cfg.CreateMap<MyViewModel, MyDbModel>()
.ForMember(dest => dest.DbModelId, opt => opt.MapFrom(src => src.ViewModelId));
cfg.CreateMap<ViewItem, IMyDbItem>()
.ConstructUsing((ViewItem src) => new MyDbItem());
cfg.CreateMap<IList<ViewItem>, IPageSelection>()
.ConstructUsing((IList<ViewItem> src) => new PageSelection())
.ForMember(dest => dest.PageSelections, opt => opt.MapFrom(src => src))
.ForMember(dest => dest.PageNumber, opt => opt.Ignore());
The error you were getting is because no mapping existed from IList<ViewItem> to IPageSelection. I've created that mapping above.
An important bit is the ConstructUsing calls. I added those here because I assumed you wanted to use the concrete types that implement the interfaces you're mapping to. Otherwise, AutoMapper will create a proxy class that implements the destination interface.
The part that enables mapping to the IPageSelection.PageSelections from IList<ViewItem> is the opt.MapFrom(src => src) part. This essentially tells AutoMapper to map from the list to the inner PageSelections property, ultimately using the mapping from ViewItem to IMyDbItem.
Example: https://dotnetfiddle.net/DW9dfO

Categories