Mapping unrelated collections into one - c#

I have an class with two collections of different unrelated types
public class Entity
{
ICollection<Foo> Foos { get; set; }
ICollection<Bar> Bars { get; set; }
}
I want to map this using AutoMapper to another class with one collection
public class DTO {
ICollection<FooBar>
}
I configure the mappings respectivitly for the two entity types into the Dto type.
.CreateMap<Foo, FooBar>()
.CreateMap<Bar, FooBar>()
How can I configure the mapping Entity -> Dto so that the two collections Foos and Bars is merged into Foobars?
If I configure them seperatly as this
.CreateMap<Entity, Dto>()
.ForMember(dest => dest.FooBars, opt => opt.MapFrom(src => src.Foos))
.ForMember(dest => dest.FooBars, opt => opt.MapFrom(src => src.Bars))
FooBars is set twice and hence overwritten by the second collection.
The question Automapper - Multi object source and one destination show ways to merge the two collections inte one in different ways, all of them requires multiple method calls when doing the actual mapping. I want to configure this so I can do the mapping by simply writing
AutoMapper.Mapper.Map<Entity, Dto>(entities);

That you need is a custom value resolver:
public class CustomResolver : IValueResolver<Source, Destination, int>
{
public int Resolve(Entity entity
, DTO dto
, ICollection<FooBar> fooBars
, ResolutionContext context)
{
// Here you should convert from entity Foos and Bars
// to ICollection<FooBar> and concat them.
}
}
Then at the setup of AutoMapper you should use the above custom resolver:
// other code
.CreateMap<Entity, Dto>()
.ForMember(dest => dest.FooBars, opt => opt.ResolveUsing<CustomResolver>());

Related

Automapper: Flattening

I've tried everything to map from Item class to ItemDto class (basically a flattening map) but I keep getting a null for ItemDto.NestedItemName:
public class Item
{
public NestedItem NestedItem{get;set;}
}
public class NestedItem
{
public string Name{get;set;}
}
public class ItemDto
{
public string NestedItemName{get;set;}
}
I would have thought this would work:
CreateMap<NestedItem, ItemDto>()
.ForMember(dest => dest.NestedItemName, opt => opt.MapFrom(src => src.Name));
but it returns null. Any ideas?
I'm using AutoMapper 7.0.1 in a .Net Core 2.1 app.
You are using the wrong mapping. More than likely it would be the item being converted to the dto so the map should be created using that
CreateMap<Item, ItemDto>()
.ForMember(
dest => dest.NestedItemName,
opt => opt.MapFrom(src => src.NestedItem.Name)
);
From comments
There is be no need for the custom mapping, the default naming conventions covers this

Automapper Dynamic Resolver's

I'm trying to create an abstract layer on top of automapper which enables users to dynamically add custom rules to each property they map.
Given the Model
public class Entity
{
public int Index { get; set; }
}
public class DTO
{
public int Count { get; set; }
}
we may configure Automapper to map the entities like so:
//sorry this is pseudo coded
cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Index,
opt => opt.ResolveUsing<IndexResolver>());
public class IndexResolver: ValueResolver<DTO, int>,
{
protected override string ResolveCore(DTO source)
{
return source.Count - 1;
}
}
This works since we map the rule to a Value resolver, However if I wanted to create a rule at run time is that possible. I would like to be able to configure things like so:
cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Index,
opt => opt.Resolver(d => d.Count - 1);
Is there a way I can Add a resolver with an expression so I do not need to inherit from ValueResolver?
My first thought is to create a generic custom resolver that takes an expression in its constructor. You should then be able to do:
.ResolveUsing(new LambdaResolver(d => d.Count - 1))

AutoMapper - Use method to update IEnumerable property with no setter?

I'm having some trouble using AutoMapper to map from a data transfer object to a database entity model. The entity has a few properties that are custom array types, derived from IEnumerable. There's no setter for those properties, but there is a method called SetFromString() that is available. I can't seem to configure my map properly to use it though. Does AutoMapper support this kind of thing? If anyone could point me in the right direction, I'd appreciate it.
Below is a boiled-down version of the key classes I'm working with. (The mapping works just fine going from entity to DTO, but I need it to work in the reverse direction as well.)
// The database entity
public class ContactEntity
{
public CustomArray<String> CustomerNumbers { get; }
}
// The data transfer object
public class ContactDto
{
public List<String> CustomerNumbers { get; set; }
}
// CustomArray definition
public abstract class CustomArray<DataType> : IEnumerable<DataType>, IDisposable
{
protected CustomArray();
public abstract void SetFromString(string Value);
}
My mapping profile is still pretty vanilla since I couldn't wrap my head around the proper ForMember syntax.
public class ContactMappingProfile : Profile
{
public ContactMappingProfile()
{
// This map works fine
CreateMap<ContactEntity, ContactDto>();
// Map from DTO to Entity
CreateMap<ContactDto, ContactEntity>()
.ForMember(dest => dest.CustomerNumbers,
opt => opt.ResolveUsing(src => src.CustomerNumbers));
}
}
Thanks again for any help you can provide!
You could use either UseDestinationValue or Ignore for destination entity CustomerNumbers member and perform the actual mapping in AfterMap:
cfg.CreateMap<ContactDto, ContactEntity>()
.ForMember(dest => dest.CustomerNumbers, opt => opt.Ignore())
.AfterMap((src, dest) => dest.CustomerNumbers.SetFromString(string.Join(",", src.CustomerNumbers)));

Automapper create map from custom method

I have the following classes:
public class Entity
{
public string Name { get; set; }
}
public class SomethingDto
{
public string NameChanged { get; set; }
public void Mapping(Entity something)
{
NameChanged = something.Name;
}
}
I want to use the Mapping Method of the DTO to create the map as the following way:
conf.CreateMap<Entity, SomethingDto>().ForMember(t => t.NameChanged, opt => opt.MapFrom(t => t.Name));
There is a way in AutoMapper to create the maps with custom methods, who works with his projection?
You don't want to do it like that, because that makes the DTO aware of the entity and that would throw out the separation you'd get.
Now in this case, the line ForMember(t => t.NameChanged, opt => opt.MapFrom(t => t.Name)) will work because Name and NameChanged are both of type string. Say you'd like to do something along the lines of mapping identifier of type string with value '20180120-00123456' to two properties on the destination: a DateTime property and a ProductId property. You can do this two ways.
Simple
You would write two mapping functions in the class where you make the mapping and do it along the lines of:
ForMember(t => t.Date, opt => opt.MapFrom(t => RetrieveDate(t.Identifier)))
ForMember(t => t.ProductId, opt => opt.MapFrom(t => RetrieveProductId(t.Identifier)))
Complex
You would make a custom class OrderIdentifier (now I'm assuming the identifier is for an order) with only the Id property as string. Then you'd make two custom type converters, like the article describes.

Can AutoMapper be "persuaded" to temporarily suspend particular mappings?

Can AutoMapper be "persuaded" to temporarily suspend particular mappings?
To illustrate what am trying to accomplish, I will use an illustration. Suppose that I have a repository, StudentRepository, that uses LINQ to interacts with database objects (tables) like Students, Courses, Activities, Clubs etc. On the application side, there are matching domain objects Student, Course, Activity, Club. The Student class contains array members of type Course, Activity, and Club like:
public class Student
{
// ... more members
public Course[] Courses { get; set; }
public Activity[] Activities { get; set; }
public Club[] Clubs { get; set; }
// ... even more members
}
AutoMapper is configured to map the database objects to the domain objects where the mappings are defined in a static constructor of StudentRepository like:
public class StudentRepository : IStudentRepository
{
static class StudentRepository
{
// ... other mappings
Mapper.CreateMap<TableStudent, Student>()
.ForMember(dest => dest.Courses, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Course>>(src.TableCourses)))
.ForMember(dest => dest.Activities, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Activity>>(src.TableActivities)))
.ForMember(dest => dest.Clubs, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Clubs>>(src.TableClubs)))
// where TableStudents, TableCourses, TableActivities, TableClubs are database entities
// ... yet more mappings
}
}
Is it possible to "persuade" AutoMapper to suspend the mappings within one function block? For example:
public Student[] GetStudents()
{
DataContext dbContext = new StudentDBContext();
var query = dbContext.Students;
// => SUSPEND CONFIGURATION MAPPINGS for Subjects, Activities and Clubs WHILE STILL making use of others
// => The idea here it to take personal charge of 'manually' setting the particular members (*for some specific reasons)
var students = Mapper.Map<Student>(query); // => Still be able to use AutoMapper to map other members
}
public Student[] OtherStudentRepositoryMethods()
{
// Other repository methods continue to make use of the mappings configured in the static constructor
}
NOTE "for some specific reasons": One reason one may want to take control away from AutoMapper would be this http://codebetter.com/davidhayden/2007/08/06/linq-to-sql-query-tuning-appears-to-break-down-in-more-advanced-scenarios/ where in the case of a 1:n associations, LINQ to SQL only supports joining-in one 1:n association per query. AutoMapper would be inefficient here - making N calls to load Courses for N students returned, N more calls to load Activities for the same N students returned, and possibly N more calls to load Clubs for the same N students returned.
Is it possible to "persuade" AutoMapper to suspend the mappings within one function block?
As I know, the best way to do it - use Ignore() like this
public class StudentRepository : IStudentRepository
{
static class StudentRepository
{
// ... other mappings
Mapper.CreateMap<TableStudent, Student>()
.ForMember(dest => dest.Courses, opt => opt.Ignore())
.ForMember(dest => dest.Activities, opt => opt.Ignore())
.ForMember(dest => dest.Clubs, opt => opt.Ignore())
// where TableStudents, TableCourses, TableActivities, TableClubs are database entities
// ... yet more mappings
}
}
Also, as it was noticed before, I'd recommend you to use different profiles for each goal you want to achieve.Here is a example
public BaseService()
{
AutoMapperRegistry.Configure();
}
public class AutoMapperRegistry
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<ServiceProfile1>();
x.AddProfile<ServiceProfileReverseProfile1>();
});
}
}
public class ServiceProfile1 : Profile
{
protected override string ProfileName
{
get
{
return "ServiceProfile1";
}
}
protected override void Configure()
{
Mapper.CreateMap<DataContract_Sub, DTO_Sub>();
Mapper.CreateMap<DataContract, DTO>()
.ForMember(x => x.DataContract_Sub, opt => opt.MapFrom(y => y.DTO_Sub))
.BeforeMap((s, d) =>
{
// your custom logic
})
.AfterMap((s, d) =>
{
// your custom logic
});
}
}
One way of achieving this would be to create separate mapping engine instances for each scenario, that way you could configure different maps, as suggested in this answer from Jimmy Bogard on wanting to map a single type in different ways.
hmm... Thanks guys for the feedback. I took time to consider all answers and suggestions. None particularly renders exactly well though they provided a lot of food for thought. I thought I should inject something I tried. (Disclaimer: My opinion is that its a dirty approach - many things could go wrong - and Murphy's laws continue to hold). You could leverage the Ignore functionality in the particular instance to "suspend" the mapping. Typically, in a try and catch block as follows:
public Student[] GetStudents()
{
try
{ // Suspend/Ignore the mappings
// => SUSPEND CONFIGURATION MAPPINGS for Subjects, Activities and Clubs
Mapper.CreateMap<TableStudent, Student>()
.ForMember(dest => dest.Courses, opt => opt.Ignore())
.ForMember(dest => dest.Activities, opt => opt.Ignore())
.ForMember(dest => dest.Clubs, opt => opt.Ignore())
DataContext dbContext = new StudentDBContext();
var query = dbContext.Students;
// other logic ...
var students = Mapper.Map<Student>(query); // => Still be able to use AutoMapper to map other members
// set the properties you needed to do manually
}
finally // Restore back the mappings
{
Mapper.CreateMap<TableStudent, Student>()
.ForMember(dest => dest.Courses, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Course>>(src.TableCourses)))
.ForMember(dest => dest.Activities, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Activity>>(src.TableActivities)))
.ForMember(dest => dest.Clubs, opt => opt.MapFrom(src => Mapper.Map<IEnumerable<Clubs>>(src.TableClubs)))
}
}
Like I mentioned, its perhaps dirty. Not the kind of code I would be happy wriing - especially since I don't know what kind of exceptional situations can arise if CreateMap() fails within the finally block, but on a legacy application where you couldn't overhaul the approach - to possibly use different profiles like suggested by #AndriyZakharko above, you could use it to get control back temporarily. I tried it out personally.

Categories

Resources