How to map empty strings as null with automapper? - c#

I need to map empty strings from source model as null to destination model.
At first I used next profile for this:
public class MyProfile:Profile
{
public MyProfile()
{
CreateMap<SrcModel, DestModel>()
.ForMember(dst => dst.Field1, opt =>
{
opt.Condition(src => !string.IsNullOrEmpty(src.src_Field1));
opt.MapFrom(src => src.src_Field1)
})
//.......
//same for other 15 fields
}
}
But duplicating same logic looks not very good and it's hard to modify it.
Also I have tried to create special map for string like this:
CreateMap<string, string>().ConvertUsing(src => string.IsNullOrEmpty(src) ?
null : src)
But such string map has impact on all my maps, but I need such logic only for several maps, not for all.
I also have tried to use ForAllMembers method:
... .ForAllMembers(opt => opt.Condition();
But there is no way do define type of source member, to cpecify some condition for strings.
What is the best way to define some common mapping logic for several members of same type for one map?

Just duplicate the logic, the most I'd do is extract the Condition part into an extension method you can call.

Related

Automapper custom value resolver reuse for multiple types

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
}

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.

Hierarchy of preferences for property in Automapper

Is it possible to set up automapper so that if the result of a MapFrom lambda is null/throws a NRE it will look at a different source. My current requirement is very basic so I've been able to do this:
.ForMember(a=>a.CountryOfRisk, m=>m.MapFrom(a=>(a.CountryOfRisk??a.Issuer.CountryOfRisk).CountryCode))
But really what I'd like to be able to do is something like this:
a=>a.CountryOfRisk, m=>m.MapFrom(a=>a.CountryOfRisk.CountryCode)
.Coalesce(a=>a.Issuer.CountryOfRisk.CountryCode))
This would mean that I could create a whole set of items which would fill the output field in a nice, config based, easy to read formatted.
It is possible to do this custom resolver:
public class CustomResolver : ValueResolver<Source, string>
{
protected override string ResolveCore(Source source)
{
return source.CountryOfRisk ?? source.Issuer.CountryOfRisk;
}
}
Mapper.Initialize(cfg =>
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.CountryOfRisk, opt => opt.ResolveUsing<CustomResolver>());
https://github.com/AutoMapper/AutoMapper/wiki/Custom-value-resolvers
(IValueResolver deprecated? ValueResolver should work).

AutoMapper objects with different property types

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.

Collate two collections using Automapper

I am looking around for a tool to automate the collation of two collections into each other and I think Automapper should work for this. We have many instances of this operation and I would like to centralize this logic into a single area.
I have the following two classes:
public class Product
{
public IEnumerable<Order> CurrentCustomerOrders { get;set; }
}
public class Order
{
order properties
}
And they are retrieved via the following calls:
_repo.GetTable<Product>();
_repo.GetTable<Order>().Where(n => n.CustomerId = _customerId);
What I want is to put all the Orders into the Products or something like this:
Mapper.CreateMap<IEnumerable<Order>, IEnumerable<Product>>()
.ForEachMember(n => n.CurrentCustomerOrders), opt => opt.MapFrom(p => p.Where(Order.ProductId == Product.ProductId))
How would I go about doing this using Automapper? Or do you know of a better tool to do this?
Thanks!
Usually you would do the filtering first using Linq or something and then do the mapping. Automapper is just a mapping tool.
e.g.
Product.CurrentCustomerOrders =
Mapper.Map<OrderDO, Order>(Orders.Where(o => o.ProductId == Product.ProductId));

Categories

Resources