Convert list of id to Object using AutoMapper - c#

I have objects :
class Group {
public string GroupName ;
public List<Access> Details ;
}
Class Access {
public int Id;
public string Name;
}
Now I have a Dto like:
Class GroupDto
{
string GroupName;
List<int> Details ;
}
User can create/send GroupDto and converted to Group before saving.
how to define the MappingProfile for this ?

In your mapping profile just use MapFrom method and let AutoMapper know how to get the data like below:
CreateMap<Group, GroupDto>()
.ForMember(
destination => destination.Details,
options => options.MapFrom(
source => source.Details.Select(detail => detail.Id).ToList()
)
);
Side Note: please expose a property instead of field like you did in your sample. Also make sure that properties on GroupDto class are public too.

Like this:
Mapper.CreateMap<Group, GroupDto>()
.ForMember(d => d.Details,
opt =>
opt.MapFrom(
s => s.Details.Select(x=>x.Id).ToList()))

Related

Automapper empty list to empty list

I have the following two classes:
I have two classes
public class SourceClass
{
public Guid Id { get; set; }
public string Provider { get; set; }
}
public class DestinationClass
{
public Guid Id { get; set; }
public List<string> Providers { get; set; }
}
I have the following mappings for my automapper
CreateMap<SourceClass, DestinationClass>()
.ForMember(destinationMember => destinationMember.Providers,
memberOptions => memberOptions.MapFrom(src => new List<string> {src.Provider ?? ""}));
CreateMap<DestinationClass, SourceClass>().ForMember(SourceClass => SourceClass.Provider,
memberOptions => memberOptions.MapFrom(src => src.Providers.FirstOrDefault()));
I wrote some unit tests and can confirm the following behaviour:
When Providers in my destination class is null, it maps to null, which is great. However, I'd like to change my mapping so that if Providers is an empty list, it maps to an empty list and similarly, if Provider is null, I'd want it to map to an empty list instead of a list with an empty string.
Does anyone know how I can go about doing this? I've tried this for my mapping from SourceClass to DestinationClass:
CreateMap<SourceClass, DestinationClass>()
.ForMember(destinationMember => destinationMember.Providers,
memberOptions => memberOptions.MapFrom(src => !string.IsNullOrEmpty(src.Provider) ? new List<string> {src.Provider} : new List<string>()));
but for going the other way, an empty list is mapping to null, instead of an empty list. (I think because of FirstOrDefault() ). Does anyone know how I can work around this?
Tested this mapping with AutoMapper 6.1.1
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceClass, DestinationClass>()
.ForMember(dest => dest.Providers,
opt => opt.MapFrom(src => string.IsNullOrEmpty(src.Provider) ? new List<string>()
: new List<string> { src.Provider }));
cfg.CreateMap<DestinationClass, SourceClass>()
.ForMember(dest => dest.Provider,
opt => opt.MapFrom(src => src.Providers == null ? ""
: src.Providers.FirstOrDefault() ?? ""));
});
If you really want it "inline" that will work. I still think it'd be easier to read if you moved it out into a method you could call, or if you could upgrade AutoMapper to leverage the built in converter mapping. Hopefully this gives you enough information to start.
Yep. I think its because its FirstOrDefaults( ) .. why not use the same ternary operator or an if statement to place your conditions for mapping ?

Is it possible to created a collection on the dest based on a collection in source?

Say I have
public class A
{
public List<int> Ids {get;set;}
}
public class B
{
public List<Category> Categories {get;set;}
}
public class Category
{
public string Name {get;set;} //will be blank on map
public int CategoryId {get;set;}
}
var source = new A {...};
var b = mapper.Map<A, B>(source);
so when mapped it will actually create a new collection on the dest but will map the ids based on what's in the source collection, other properties of the dest will be blank because there is nothing to map from.
How to setup the configuration to do this mapping?
You need a combination of ForMember, MapFrom and ForAllOtherMembers:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<A, B>()
.ForMember(dest => dest.Categories, opt => opt.MapFrom(src => src.Ids));
cfg.CreateMap<int, Category>()
.ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src))
.ForAllOtherMembers(opt => opt.Ignore());
});
MapFrom will allow you to override the default mapping-by-name that AM normally does. As you can see in line 4, we can say that Ids in the source maps to Categories in the destination class.
But now you need to override how an int gets mapped, since that is the type of thing in Ids. With MapFrom, you don't (necessarily) have to provide a property for the source--the entire source itself can be the thing being mapped. So in line 7, we are mapping the ints that came from the mapping in line 4 and saying that they should map to the destination class' CategoryId property. Finally, we simply tell AM that we don't care to map any remaining properties in the target class by specifying the ForAllOtherMembers option.

Automapper expression mapping

I am trying to perform the following Automapper mapping for an OrderBy:
Expression<Func<ServerObject, object>> serverQueryable = x => x.TestEnumKVP.Value;
Mapper.Map<Expression<Func<ServerObject, object>>, Expression<Func<DatabaseObject, object>>(serverQueryable)
I want to map the ServerObject expression to a DatabaseObject expression
ServerObject defined as:
public class ServerObject
{
public KeyValuePairEx TestEnumKVP { get; set; }
}
KeyValuePairEx is a wrapper for the Enumeration which stores the Int16 value and the string value:
public enum TestEnum : Int16 { Test1, Test2, Test3 }
public class KeyValuePairEx
{
internal KeyValuePairEx(TestEnum key, string value) { }
public TestEnum Key { get; set; }
public string Value { get; set; }
}
DatabaseObject defined as:
public class DatabaseObject
{
public string TestEnumId { get; set; }
}
The Mapping I have is:
AutoMapper.Mapper.Initialize(config =>
{
config.CreateMap<DatabaseObject, ServerObject>().ForMember(dest => dest.TestEnumKVP.Value, opt => opt.MapFrom(src => src.TestEnumId));
});
The mapping fails with:
'Expression 'dest => dest.TestEnumKVP.Value' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.'
I need ServerObject.TestEnumKVP.Value to Map to DatabaseObject.TestEnumId. I am aware that Expression mappings are reversed - hence why the Map is from DatabaseObject to ServerObject. I have spent many hours on this and am at a loss as to how to get the mapping to work!
NB. I am using AutoMapper 6.1.1
Any help would be appreciated!
Thank you Lucian, I followed the github link and the solution offered by Blaise has worked. See below:
CreateMap<DatabaseObject, ServerObject>().ForMember(dest => dest.TestEnumKVP, opt => opt.MapFrom(src => src));
CreateMap<DatabaseObject, KeyValuePairEx>().ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.TestEnumId));
I was starting to look for at workarounds so delighted it was possible and that the solution was so clean and concise.
Thanks again!
The error and the solution are right there in the message. Forget about all the expression stuff. The ForMember is broken. Try ForPath instead.
Expression mapping now supports ForPath. See https://github.com/AutoMapper/AutoMapper/issues/2293.

Automapper convention based mapping for collection

I have a project where I am trying to map a dictionary to a ViewModel.NamedProperty. I am trying to use an AutoMapper custom resolver to perform the mapping based on a convention. My convention is that if the named property exists for the source dictionary key then map a property from the dictionary's value. Here are my example classes:
class User
{
string Name {get;set;}
Dictionary<string, AccountProp> CustomProperties {get;set;}
}
class AccountProp
{
string PropertyValue {get;set;}
//Some other properties
}
class UserViewModel
{
string Name {get;set;}
DateTime LastLogin {get;set;}
string City {get;set}
}
var user = new User()
{
Name = "Bob"
};
user.CustomProperties.Add("LastLogin", new AccountProp(){PropertyValue = DateTime.Now};
user.CustomProperties.Add("City", new AccountProp(){PropertyValue = "SomeWhere"};
I want to map the User CustomProperties dictionary to the flattened UserViewModel by convention for all properties and I do not want to specify each property individually for the mapping.
What is the best way to go about this? I was thinking Custom value resolver but it seems that I have to specify each member I want to map individually. If I wanted to do that I would just manually perform the mapping without AutoMapper.
Below is code that serve the purpose. Not sure whether it is good or not.
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) // Optional
.ForMember(dest => dest.City, opt => opt.MapFrom(src => src.CustomProperties.FirstOrDefault(x => x.Key == "City").Value.PropertyValue.ToString())) // Handle null
.ForMember(dest => dest.LastLogin, opt => opt.MapFrom(src => Convert.ToDateTime(src.CustomProperties.FirstOrDefault(x => x.Key == "LastLogin").Value.PropertyValue))); //Handle null
I ended up creating a custom type converter to deal with this scenario and it works great:
public class ObjectToPropertyTypeConverter<TFromEntity> : ITypeConverter<TFromEntity, HashSet<Property>>
{
//perform custom conversion here
}
I then implemented the Custom mapping as follows:
AutoMapper.Mapper.CreateMap<MyViewModel, HashSet<Property>>()
.ConvertUsing<ObjectToPropertyTypeConverter<MyViewModel>>();

How to use AutoMapper?

First time using AutoMapper and I'm have a hard time figuring out how to use it.
I'm trying to map a ViewModel to my Database Tables.
My ViewModel looks like this...
public class AddressEditViewModel
{
public AddressEdit GetOneAddressByDistrictGuid { get; private set; }
public IEnumerable<ZipCodeFind> GetZipCodes { get; private set; }
public AddressEditViewModel(AddressEdit editAddress, IEnumerable<ZipCodeFind> Zips)
{
this.GetOneAddressByDistrictGuid = editAddress;
this.GetZipCodes = Zips;
}
}
The Mapping I'm trying to use is...
CreateMap<Address, AddressEditViewModel>();
When I run this test...
public void Should_map_dtos()
{
AutoMapperConfiguration.Configure();
Mapper.AssertConfigurationIsValid();
}
I get this error...
AutoMapper.AutoMapperConfigurationException: The following 2 properties on JCIMS_MVC2.DomainModel.ViewModels.AddressEditViewModel
are not mapped:
GetOneAddressByDistrictGuid
GetZipCodes
Add a custom mapping expression, ignore, or rename the property on JCIMS_MVC2.DomainModel.Address.
I'm not sure how I am supposed to map those 2 properties. I would appreciate any direction. Thanks
Mark
Ok so I can see a few things you are doing that probably won't help.
Firstly this AutoMapper is used to copy Properties in one object to Properties in a diff object. Along the way it might interrogate or manipulate them to get the end result viewmodel in the correct state.
The properties are named 'Get...' which sounds more like a method to me.
The setters on your properties are private so AutoSetter won't be able to find them. Change these to minimum internal.
Use of a parametrized constructor is no longer needed when you use AutoMapper - as you are converting directly from one object to another. The parametised constructor is there mainly to show what is explicitly required by this object.
CreateMap<Address, AddressEditViewModel>()
.ForMember( x => x.GetOneAddressByDistrictGuid ,
o => o.MapFrom( m => m."GetOneAddressByDistrictGuid") )
.ForMember( x => x.GetZipCodes,
o => o.MapFrom( m => m."GetZipCodes" ) );
What Automapper is really good for is copying from DataObjects into POCO objects, or View Model objects.
public class AddressViewModel
{
public string FullAddress{get;set;}
}
public class Address
{
public string Street{get;set;}
public string Suburb{get;set;}
public string City{get;set;}
}
CreateMap<Address, AddressViewModel>()
.ForMember( x => x.FullAddress,
o => o.MapFrom( m => String.Format("{0},{1},{2}"), m.Street, m.Suburb, m.City ) );
Address address = new Address(){
Street = "My Street";
Suburb= "My Suburb";
City= "My City";
};
AddressViewModel addressViewModel = Mapper.Map(address, Address, AddressViewModel);

Categories