I am struggling to create a DateTime object from (year, month, day) which is being returned from the database. I am rather new to AutoMapper so a nudge in the right direction would be great.
Here is the ViewModel containing the DateTime object and the three values that need to be used to create the DateTime:
public class EnquiriesListViewModel
{
// other field elided
public sbyte flightDay;
public sbyte flightMonth;
public bool flightYear
public DateTime flightDate;
// other field elided
}
I would like AutoMapper to construct the flightDate from the other three values. I have tried various approaches, some of which didn't even compile!
Like this:
Mapper.CreateMap<enquiryListEntry, EnquiriesListViewModel>()
.ForMember(dest => dest.flightDate, /* what goes in here? */);
Looking forward to your responses.
M
Mapper.CreateMap<enquiryListEntry, EnquiriesListViewModel>()
.ForMember(dest => dest.flightDate, opt => opt.MapFrom(src => new DateTime(src.flightYear, src.flightMonth, src.flightDay)));
Should do it.
This solution is coming too late but its good because it applies to .NET 4.6.1
Mapper.CreateMap<enquiryListEntry, EnquiriesListViewModel>()
.ForMember(dest => dest.flightDate,
opt => opt.AddTransform(src => new DateTime(src.Year,
src.Month,
src.Day)));
Related
I have a project which I am trying to use AutoMapper to map from multiple classes in each of these classes there are properties where I would like to use some custom logic to parse the source value to the destination.
I have tried to use custom resolver methods as documented on the AutoMapper docs.
Here is my code:
public class CustomDateTextHandler : IValueResolver<object, object, string>
{
public string Resolve(object source, object destination, string destMember, ResolutionContext context)
{
string txt = source.ToString();
txt.Replace("AM/PM", "tt");
txt.Replace("HH:MM", "hh:mm");
if (txt.Contains("format"))
{
txt.Replace("mmm", "MMM");
}
return txt;
}
}
public class SMapping : Profile
{
public SMapping()
{
CreateMap<SourceForm, s_form>()
.ForMember(dest => dest.id, opt => opt.Ignore())
.ForMember(dest => dest.cell_text, opt => opt.MapFrom<CustomDateTextHandler>())
.ForMember(dest => dest.fn_def, opt => opt.MapFrom<CustomCodeTextResolver>());
}
What I am trying to get is the cell_text value processed with my replace logic in the resolver method but the issue I am facing is that what is being passed to the resolver is the entire SMapping instance, I would like to be able to reuse the resolver code across different classes where the property names will be different, however looking at what it going on at the moment I could not really use the resolver code across my different classes.
Can someone help me?
Thank you in advance.
Use IMemberValueResolver instead of IValueResolver.
Compared to IValueResolver, its Resolve function gets one more parameter: value.
Registering mapping with IMemberValueResolver requires you to pass 1 extra parameter - not the 'value' directly, but a lambda that will produce a 'value' from given source object.
public class CustomDateTextHandler :
IMemberValueResolver< // note: different interface
object, object,
string, string // note: 1 more parameter
>
{
public string Resolve(
object source, object destination,
string sourceValue, string destMember, // note: 1 more parameter
ResolutionContext context
)
{
// here, see the difference:
// source - source object, whole
// sourceMember - value produced by extra lambda passed in mapping
}
}
public class SMapping : Profile
{
public SMapping()
{
CreateMap<SourceForm, s_form>()
...
.ForMember(
dest => dest.cell_text,
opt => opt.MapFrom<CustomDateTextHandler, string>(source => source.PROPERTY11)) // note: this produces that sourceValue
.ForMember(
dest => dest.fn_def,
opt => opt.MapFrom<CustomCodeTextResolver, string>(source => source.PROPERTY22)); // note: this produces that sourceValue
}
I have 12 integers representing 12 months in an old database, and I need to map them to an array/list. The problem is I'm not sure how I can initialize the array in the model to size 12, in order to map it.
Here's what I'm trying to do:
Model:
public class Year
{
public int[] Months { get; set; } //How do I initialize to 12?
}
Mapping:
CreateMap<DataRow, Year>()
.ForMember(dest => dest.Months[0], opt => opt.MapFrom(src => src["Jan"]))
.ForMember(dest => dest.Months[1], opt => opt.MapFrom(src => src["Feb"]))
.ForMember(dest => dest.Months[2], opt => opt.MapFrom(src => src["Mar"]))
I've searched everywhere for pre-initialized arrays in models, but couldn't find anything on the syntax.
Very simple:
public class Year
{
public int[] Months { get; } = new int[12];
}
I also suggest to remove setter, make it as read only property - does not change ability to set individual items in array itself.
Is there a way to pass the properties I want to retrieve from the DB in a Select dynamically, I don't know the properties I need beforehand and I don't want to write the conditions in my repository.
I don't want to retrieve all the fields at once, just the ones I need based on some conditions.
For example:
public class Student
{
public string Property1 {get; set;}
public string Property2 {get; set;}
//other properties here
[NotMapped]
public string Selected
{
if(condition)
return Property1;
else
return Property2;
}
}
and in the service layer I have
query.Select(s => new StudentViewModel
{
Value = s.Selected; //this one will get the property we want based on a condition
//other stuff here
//OtherValue = s.OtherProperty
}
).FirstOrDefault();
Selector
An easy but ugly way is to use a Selector:
query.Select(Selector()).FirstOrDefault();
And the Selector can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if (Condition())
return x => new StudentViewModel
{
Name = x.Property1,
OtherName = x.OtherName
};
return x => new StudentViewModel
{
Name = x.Property2,
OtherName = x.OtherName
};
}
As you can see the obviously downside here is that you need to copy/paste all other selected properties. That is why its ugly.
AutoMapper
Configs
You can use AutoMapper with different configurations. First you need to define a standard mapping for all properties that don't need to be dynamic.
public static void AddStandardStudentMap(this IMappingExpression<Student, StudentViewModel> map)
{
map.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherProperty))
.ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherProperty2));
// you can concat .ForMember() for each property you need.
}
Next, you need to define the different configs and add the AddStandardStudentMap method to each invidual mapping.
var config1 = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property1))
.AddStandardStudentMap()
);
var config2 = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Property2))
.AddStandardStudentMap()
);
After this, just use your conditions to decide which config do you need
IConfigurationProvider provider;
if(Condition())
provider = config1;
else
provider = config2;
And then instead of .Select() use:
query.ProjectTo<StudentViewModel>(provider).FirstOrDefault();
As we can see this solution is still ugly and has a lot of overhead but its needed in some cases, thats why i stated it here.
Expression
This is a bit similar to the Configs but brings you more flexibility and less writing effort.
First create a config but this time with a Selector
var config = new MapperConfiguration(cfg =>
cfg.CreateMap<Student, StudentViewModel>()
.ForMember(dest => dest.OtherName, opt => opt.MapFrom(src => src.OtherName))
.ForMember(dest => dest.OtherName2, opt => opt.MapFrom(src => src.OtherName2))
// and so on. Map all your properties that are not dynamically.
// and then the Selector
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => Selector()))
);
The Selector method can look like this:
private static Expression<Func<Student, StudentViewModel>> Selector()
{
if(Condition())
return src => src.Property1;
else
return src => src.Property2;
}
And then just use it like the configs solution but without selecting a particular config:
query.ProjectTo<StudentViewModel>(config).FirstOrDefault();
Conclusion
I know this is a lot input and there are even more possibilities to achieve the behaviour that you want, with or without AutoMapper. But i would suggest you (grounded on the information you gave us) to use AutoMapper with Expressions. It should give the flexibility you need and is extensible for further requirements.
I want to map my Entity Framework entities (generated from a legacy database) to custom DTO objects (which should be nice and clean).
My legacy DB has entities looking a bit like this:
internal class Order {
int id;
string Shipping_date;
string quantity;
}
And I want to map it to a nicer DTO object:
public class OrderDto {
int id;
DateTime? ShippingDate;
int Quantity;
}
I have written an "entity container" to provide dependency injection, which returns values this way:
public IEnumerable<OrderDto> GetPaginatedOrders(int page, int pageSize)
{
return this.db.Orders
.OrderByDescending(c => c.id)
.Paginate(page, pageSize)
.Project()
.To<OrderDto>()
.AsEnumerable();
}
So: change of types, and change of property names.
Were it only change of property names, it would be easy-but-tedious:
Mapper.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.quantity))
.ForMember(dest => dest.ShippingDate, opt => opt.MapFrom(src => src.Shipping_date));
This is not enough with type changes. I tried a whole bunch of stuff:
Parsing the properties at the mapping declaration, like src => int.Parse(src.quantity) but Linq doesn't like it.
Extending the EF entities with custom properties like QuantityInt { get { return int.Parse(this.quantity) } } and using these in the mapping, but AutoMapper doesn't like it, and explicitly don't support them.
Mapping system types one to another like Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32) but I still get Unable to create a map expression from System.String to System.Int32 errors.
Using custom converters for my class, but I always get empty values from ResolutionContext.SourceValues at run-time from my entities (I'm guessing that they are disposed before AutoMapper gets them or something like this).
I'm realizing that AutoMapper is convention-based, so maybe I should use another tool, but which one exist?
Thanks for your help!
.Project() uses Linq to entities, which generates SQL and naturally only understands a very limited set of functions.
If you use
Mapper.Map<IEnumerable<Order>, IEnumerable<OrderDto>>(src)
your conversions will work fine.
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.