Map Entity Framework collection to comma delimted string with automapper - c#

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

Related

Getting error while assigning Source List to Destination List values in Composite Inner classes

new for AutoMapper. I have a Composite class structure as below. I am trying to assign the OtherInnerSource List to InnerDestination List but am not able to initialize (Composite Classes properties) and getting an error.
class Source
{
public string Name { get; set; }
public InnerSource InnerSource { get; set; }
public List<OtherInnerSource> OtherInnerSources { get; set; } = new List<OtherInnerSource>();
}
class InnerSource
{
public string Name { get; set; }
public string Description { get; set; }
}
class OtherInnerSource
{
public string Name { get; set; }
public string Description { get; set; }
public string Title { get; set; }
}
class Destination
{
public string Name { get; set; }
public List<InnerDestination> InnerDestinations { get; set; }
}
class InnerDestination
{
public string Description { get; set; }
public string Title { get; set; }
}
my AutoMapper configuration is like this. Can you please help me with how to initialize the properties if we have Lists?
public void ComplexAssignmentData()
{
try
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>().IncludeMembers(src => src.InnerSource, src => src.OtherInnerSources);
cfg.CreateMap<InnerSource, Destination>(MemberList.None).ForPath(dest => dest.InnerDestinations.First().Description, opt =>
{
opt.MapFrom(s => s.Description);
opt.Condition(c => true);
});
cfg.CreateMap<OtherInnerSource, Destination>(MemberList.None).ForPath(dest => dest.InnerDestinations.First().Title, opt =>
{
opt.MapFrom(OtherSrc => OtherSrc.Title);
opt.Condition(c => true);
});
});
var source = new Source
{
Name = "name",
InnerSource = new InnerSource { Description = "description" },
OtherInnerSources = { new OtherInnerSource { Title = "title" } }
};
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
var destination = mapper.Map<Destination>(source);
var plan = config.BuildExecutionPlan(typeof(Source), typeof(Destination));
}
catch (Exception ex)
{
}
}
Iam getting error '
Only member accesses are allowed. dest => dest.InnerDestinations.First().Description (Parameter 'destinationMember')'
while initializing from Source List(List) to Destination List(List).
How to resolve this problem.
I don't think you can call First() on the destination like that because the destination doesn't exist yet. Simply map the collection.
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.InnerDestinations, opt => opt.MapFrom(source => source.OtherInnerSources));
cfg.CreateMap<OtherInnerSource, InnerDestination>();

Mapping viewmodel children with parameter AutoMapper

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; }
}
}

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);

Automapper - Not copying from source to destination object correctly. Errors need default constructor

When I map a source object into an existing instance it appears to want to create a new object and not copy into...
Here is my test scenario:
List<DomainCustomerProxy> domainCustomers = customers.Select(customer => new DomainCustomerProxy(
new Lazy<List<DomainContact>>(() => RetrieveContactsByCustomerId(customer.Id, lazyLoad, schemaName).ToList(), LazyThreadSafetyMode.ExecutionAndPublication),
new Lazy<List<DomainDepartment>>(() => RetrieveDepartmentsByCustomerId(customer.Id, lazyLoad, schemaName).ToList(), LazyThreadSafetyMode.ExecutionAndPublication),
new Lazy<List<DomainGroup>>(() => RetrieveCustomerGroupsByCustomerId(customer.Id, lazyLoad, schemaName).ToList(), LazyThreadSafetyMode.ExecutionAndPublication)
)
{
Id = customer.Id,
}).ToList();
domainCustomers = Mapper.Map(customers, domainCustomers);
return domainCustomers;
I get this error:
Type 'Raven.Lib.Domain.CRM.Models.CustomerProxy' does not have a default constructor
public class CustomerProxy : Customer
{
public CustomerProxy(Lazy<List<Contact>> contacts, Lazy<List<Department>> departments, Lazy<List<Group>> groups)
{
_contactLazyList = contacts;
_departmentLazyList = departments;
_groupLazyList = groups;
}
private readonly Lazy<List<Contact>> _contactLazyList;
public override List<Contact> Contacts
{
get { return _contactLazyList.Value; }
}
private readonly Lazy<List<Department>>_departmentLazyList;
public override List<Department> Departments
{
get { return _departmentLazyList.Value; }
}
private readonly Lazy<List<Group>> _groupLazyList;
public override List<Group> CustomerGroups
{
get { return _groupLazyList.Value; }
}
}
customer base class:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
public int CreatedById { get; private set; }
public int ModifiedById { get; private set; }
public DateTime DateCreated { get; private set; }
public DateTime DateModified { get; private set; }
public int Version { get; private set; }
public virtual List<Contact> Contacts { get; set; }
public virtual List<Department> Departments { get; set; }
public virtual List<Group> CustomerGroups { get; set; }
public SerializableDictionary<string, string> ValidationErrors { get; set; }
public bool Validate()
{
ValidationErrors = new SerializableDictionary<string, string>();
if (string.IsNullOrWhiteSpace(Name))
{
ValidationErrors.Add(LambdaHelper<Customer>.GetPropertyName(x => x.Name), "Customer.Name is a mandatory field.");
}
if (Number <= 0)
{
ValidationErrors.Add(LambdaHelper<Customer>.GetPropertyName(x => x.Number), "Customer.Number is a mandatory field and can not be 0.");
}
return ValidationErrors.Count > 0;
}
}
Now if I create a empty default constructor
protected CustomerProxy(){ }
It does not error but the appears to wipe out my old instance here is the proof.
Mapping ignoring the properties I dont want overwritten.
CreateMap<DaoCustomer, DomainCustomerProxy>()
.ForMember(dest => dest.Contacts, opt => opt.Ignore())
.ForMember(dest => dest.CustomerGroups, opt => opt.Ignore())
.ForMember(dest => dest.Departments, opt => opt.Ignore())
.ForMember(dest => dest.ValidationErrors, opt => opt.Ignore());
Before:
After:
Am I using automapper incorrectly?
domainCustomers = Mapper.Map(customers, domainCustomers);
Little back ground:
I am using the virtual proxy pattern for lazy loading my contacts,departments, and groups as these are very large lists. This is why I inherit from Customer which is the base class.

Categories