AutoMapper and IDataReader - c#

I would like to map data from IDateReader to some class but can not do it simply. I wrote the following code:
cfg.CreateMap<IDataReader, MyDto>()
.ForMember(x => x.Id, opt => opt.MapFrom(rdr => rdr["Id"]))
.ForMember(x => x.Name, opt => opt.MapFrom(rdr => rdr["Name"]))
.ForMember(x => x.Text, opt => opt.MapFrom(rdr => rdr["Text"]));
UPD: I tried to use Automapper.Data from Nuget but it depends on
NETStandard.Library but I use .NET Framework 4.5
But this way is bad for me because I have to describe mapping rule for each column. Is it possible to avoid describing all of these rules?

You could use an ITypeConverter, such as the following:
public class DataReaderConverter<TDto> : ITypeConverter<IDataReader, TDto> where TDto : new
{
public TDto Convert(IDataReader source, TDto destination, ResolutionContext context)
{
if (destination == null)
{
destination = new TDto();
}
typeof(TDto).GetProperties()
.ToList()
.ForEach(property => property.SetValue(destination, source[property.Name]));
}
}
cfg.CreateMap<IDataReader, MyDto>().ConvertUsing(new DataReaderConverter<MyDto>());

Related

Automapper conditional map from multiple source fields

I've got a source class like the following:
public class Source
{
public Field[] Fields { get; set; }
public Result[] Results { get; set; }
}
And have a destination class like:
public class Destination
{
public Value[] Values { get; set; }
}
So I want to map from EITHER Fields or Results to Values depending on which one is not null (only one will ever have a value).
I tried the following map:
CreateMap<Fields, Values>();
CreateMap<Results, Values>();
CreateMap<Source, Destination>()
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Fields != null);
opt.MapFrom(s => s.Fields });
})
.ForMember(d => d.Values, opt =>
{
opt.PreCondition(s => s.Results != null);
opt.MapFrom(s => s.Results);
});
Only issue with this is that it looks if the last .ForMember map doesn't meet the condition it wipes out the mapping result from the first map.
I also thought about doing it as a conditional operator:
opt => opt.MapFrom(s => s.Fields != null ? s.Fields : s.Results)
But obviously they are different types so don't compile.
How can I map to a single property from source properties of different types based on a condition?
Thanks
There is a ResolveUsing() method that allows you for more complex binding and you can use a IValueResolver or a Func. Something like this:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.ResolveUsing<ConditionalSourceValueResolver>());
And the value resolver depending on your needs may look like:
public class ConditionalSourceValueResolver : IValueResolver<Source, Destination, Value[]>
{
public Value[] Resolve(Source source, Destination destination, Value[] destMember, ResolutionContext context)
{
if (source.Fields == null)
return context.Mapper.Map<Value[]>(source.Results);
else
return context.Mapper.Map<Value[]>(source.Fields);
}
}
Following #animalito_maquina answer.
Here is an update for 8.0 Upgrade:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Values, mo => mo.MapFrom<ConditionalSourceValueResolver>());
And to save you time, ValueResolvers are not supported for Queryable Extensions
ResolveUsing is not available, try this.
It's working for me
CreateMap<Source, Destination>()
.ForMember(opt => opt.value, map =>
map.MapFrom((s, Ariel) => s.Fields != null ? s.Fields : s.Results));

Automapper. Map if source member is null

I have two classes and map one to other with Automapper. For instance:
public class Source
{
// IdName is a simple class containing two fields: Id (int) and Name (string)
public IdName Type { get; set; }
public int TypeId {get; set; }
// another members
}
public class Destination
{
// IdNameDest is a simple class such as IdName
public IdNameDest Type { get; set; }
// another members
}
Then I use Automapper to map Source to Destination:
cfg.CreateMap<Source, Destination>();
It works properly but sometimes member Type in class Source becomes null. In these cases I would like to map member Type in class Destination from TypeId property. That's what I want in a nutshel:
if Source.Type != null
then map Destination.Type from it
else map it as
Destination.Type = new IdNameDest { Id = Source.Id }
Is it possible with AutoMapper?
You can use the .ForMember() method while declaring the mapping.
Like so :
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type != null ? src.Type : new IdNameDest { Id = src.Id }));
While LeeeonTMs answer works fine AutoMapper provides a specialised mechanism to substitute null values. It "allows you to supply an alternate value for a destination member if the source value is null anywhere along the member chain" (taken from the AutoMapper manual).
Example:
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Value, opt => opt.NullSubstitute(new IdNameDest { Id = src.Id }));
With C# 6.0, the null-coalescing operator can be used.
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type ?? new IdNameDest { Id = src.Id }));
I managed to resolve it with Mapping Resolvers
public class SomeResolver : IValueResolver<Soruce, Dest, Guid>
{
public Guid Resolve(Source source, Dest destination, Guid, destMember, ResolutionContext context)
{
destination.Value= source.Value!=null ? source.Value:0;
return destination.MainGuid = Guid.NewGuid();
}
}
and then on mapping configuraiton
CreateMap<BioTimeEmployeeSummaryDTO, BioTimeEmployeeAttendanceSummary>()
.ForMember(dest => dest.MainGuid, opt => opt.MapFrom<YourResolverClass>())
.ReverseMap();

Dynamically passing properties in Select for projection in Entity Framework

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.

Automapper base class mapping

For AutoMapper, I've implemented the following which works, but has repeated mapping code:
cfg.CreateMap<RmsOelEntryUnlinkedPersonInputDto, AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedPersonUnlinked>()
.ForMember(dest => dest.DateOfBirth, opts => opts.MapFrom(src => FormatDateType(src.DateOfBirth)))
.ForMember(dest => dest.EffectiveFromTime, opts => opts.MapFrom(src => FormatDateTimeType(src.EffectiveFromTime)))
.ForMember(dest => dest.EffectiveToTime, opts => opts.MapFrom(src => FormatDateTimeType(src.EffectiveToTime)));
cfg.CreateMap<RmsOelEntryUnlinkedAddressInputDto, AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedAddressUnlinked>()
.ForMember(dest => dest.EffectiveFromTime, opts => opts.MapFrom(src => FormatDateTimeType(src.EffectiveFromTime)))
.ForMember(dest => dest.EffectiveToTime, opts => opts.MapFrom(src => FormatDateTimeType(src.EffectiveToTime)));
Both RmsOelEntryUnlinkedPersonInputDto and RmsOelEntryUnlinkedAddressInputDto are inherited from RmsOelEntryInvolvedEntityBaseDto and this base class has the properties EffectiveFromTime and EffectiveToTime.
I don't want the to have to repeatedly map EffectiveFromTime and EffectiveToTime as seen above.
However, AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedPersonUnlinked and AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedAddressUnlinked are autogenerated and don't have a base class. Therefore I don't see how I can use AutoMapper's "Include" mapping option.
How do I optimise this to remove the repeated mapping?
I had similar situation and have resorted to helper Extension methods. I customized for your case :
internal static class CommonMapperExtensions
{
internal static IMappingExpression<TSource, TDestination> MapCommonFields<TSource, TDestination>(this IMappingExpression<TSource, TDestination> m)
where TSource : RmsOelEntryInvolvedEntityBaseDto
where TDestination : IEffective
{
return m
.ForMember(dest => dest.EffectiveFromTime, opts => opts.MapFrom(src => FormatDateTimeType(src.EffectiveFromTime)))
.ForMember(dest => dest.EffectiveToTime, opts => opts.MapFrom(src => FormatDateTimeType(src.EffectiveToTime)));
}
}
Then configuration would look like this :
cfg.CreateMap<RmsOelEntryUnlinkedPersonInputDto, AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedPersonUnlinked>()
.MapCommonFields()
.ForMember(dest => dest.DateOfBirth, opts => opts.MapFrom(src => FormatDateType(src.DateOfBirth)));
cfg.CreateMap<RmsOelEntryUnlinkedAddressInputDto, AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedAddressUnlinked>()
.MapCommonFields();
If AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedAddressUnlinked and AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedPersonUnlinked are autogenerated I would assume they are partial.
Then you could make common interface for those classes :
public interface IEffective
{
DateTime EffectiveFromTime {get; set;}
DateTime EffectiveToTime {get; set;}
}
public partial AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedAddressUnlinked
: IEffective { }
public partial AddNewOELEntryInvolvedEntitiesUnlinkedInvolvedPersonUnlinked
: IEffective { }
public class TBaseModel
{
public void ConfigureMapFrom(IMappingExpression<TEntity, TBaseModel> mapping)
{
// ... mappings
}
}
public class TModel : TBaseModel
{
public void ConfigureMapFrom(IMappingExpression<TEntity, TModel> mapping)
{
mapping.IncludeBase<TEntity, TBaseModel>();
// ... other mappings
}
}

AutoMapper: two-way, deep mapping, between domain models and viewmodels

I need to map two ways between a flat ViewModel and a deep structured Domain model. This will be a common scenario in our solution.
My models are:
public class Client
{
...
public NotificationSettings NotificationSettings { get; set; }
public ContactDetails ContactDetails { get; set; }
...
}
public class NotificationSettings
{
...
public bool ReceiveActivityEmails { get; set; }
public bool ReceiveActivitySms { get; set; }
...
}
public class ContactDetails
{
...
public string Email { get; set }
public string MobileNumber { get; set; }
...
}
public class ClientNotificationOptionsViewModel
{
public string Email { get; set }
public string MobileNumber { get; set; }
public bool ReceiveActivityEmails { get; set; }
public bool ReceiveActivitySms { get; set; }
}
Mapping code:
Mapper.CreateMap<Client, ClientNotificationOptionsViewModel>()
.ForMember(x => x.ReceiveActivityEmails, opt => opt.MapFrom(x => x.NotificationSettings.ReceiveActivityEmails))
.ForMember(x => x.ReceiveActivitySms, opt => opt.MapFrom(x => x.NotificationSettings.ReceiveActivitySms))
.ForMember(x => x.Email, opt => opt.MapFrom(x => x.ContactDetails.Email))
.ForMember(x => x.MobileNumber, opt => opt.MapFrom(x => x.ContactDetails.MobileNumber));
// Have to use AfterMap because ForMember(x => x.NotificationSettings.ReceiveActivityEmail) generates "expression must resolve to top-level member" error
Mapper.CreateMap<ClientNotificationOptionsViewModel, Client>()
.IgnoreUnmapped()
.AfterMap((from, to) =>
{
to.NotificationSettings.ReceiveActivityEmail = from.ReceiveActivityEmail;
to.NotificationSettings.ReceiveActivitySms = from.ReceiveActivitySms;
to.ContactDetails.Email = from.Email;
to.ContactDetails.MobileNumber = from.MobileNumber;
});
...
// Hack as ForAllMembers() returns void instead of fluent API syntax
public static IMappingExpression<TSource, TDest> IgnoreUnmapped<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
{
expression.ForAllMembers(opt => opt.Ignore());
return expression;
}
I dislike it because:
1) It is cumbersome
2) The second mapping pretty much dismantles Automapper's functionality and implements the work manually - the only advantage of it is consistency of referencing Automapper throughout the code
Can anyone suggest:
a) A better way to use Automapper for deep properties?
b) A better way to perform two-way mapping like this?
c) Advice on whether I should bother using Automapper in this scenario? Is there a compelling reason not to revert to the simpler approach of coding it up manually? eg.:
void MapManually(Client client, ClientNotificationOptionsViewModel viewModel)
{
viewModel.Email = client.ContactDetails.Email;
// etc
}
void MapManually(ClientNotificationOptionsViewModel viewModel, Client client)
{
client.ContactDetails.Email = viewModel.Email;
// etc
}
-Brendan
P.S. restructuring domain models is not the solution.
P.P.S It would be possible to clean up the above code through extension methods & some funky reflection to set deep properties... but I'd rather use automapper features if possible.
This can be done also in this way:
Mapper.CreateMap<ClientNotificationOptionsViewModel, Client>()
.ForMember(x => x.NotificationSettings, opt => opt.MapFrom(x => new NotificationSettings() { ReceiveActivityEmails = x.ReceiveActivityEmails, ReceiveActivitySms = x.ReceiveActivitySms}))
.ForMember(x => x.ContactDetails, opt => opt.MapFrom(x => new ContactDetails() { Email = x.Email, MobileNumber = x.MobileNumber }));
But is not much different than your solution.
Also, you can do it by creating a map from your model to your inner classes:
Mapper.CreateMap<ClientNotificationOptionsViewModel, ContactDetails>();
Mapper.CreateMap<ClientNotificationOptionsViewModel, NotificationSettings>();
Mapper.CreateMap<ClientNotificationOptionsViewModel, Client>()
.ForMember(x => x.NotificationSettings, opt => opt.MapFrom(x => x))
.ForMember(x => x.ContactDetails, opt => opt.MapFrom(x => x));
You don't need to specify ForMember in the new mappings because the properties has the same name in both classes.
In the end I found AutoMapper was unsuited to my scenario.
Instead I built a custom utility to provide bidirectional mapping & deep property mapping allowing configuration as follows. Given the scope of our project I believe this is justified.
BiMapper.CreateProfile<Client, ClientNotificationsViewModel>()
.Map(x => x.NotificationSettings.ReceiveActivityEmail, x => x.ReceiveActivityEmail)
.Map(x => x.NotificationSettings.ReceiveActivitySms, x => x.ReceiveActivitySms)
.Map(x => x.ContactDetails.Email, x => x.Email)
.Map(x => x.ContactDetails.MobileNumber, x => x.MobileNumber);
BiMapper.PerformMap(client, viewModel);
BiMapper.PerformMap(viewModel, client);
Apologies I cannot share the implementation as it's commercial work. However I hope it helps others to know that it isn't impossible to build your own, and can offer advantages over AutoMapper or doing it manually.

Categories

Resources