AutoMapper: What is the difference between ForMember() and ForPath()? - c#

I am reading AutoMapper's ReverseMap() and I can not understand the difference between ForMember() and ForPath(). Implementations was described here. In my experience I achieved with ForMember().
See the following code where I have configured reverse mapping:
public class Customer
{
public string Surname { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class CustomerDto
{
public string CustomerName { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Customer, CustomerDto>()
.ForMember(dist => dist.CustomerName, opt => opt.MapFrom(src => $"{src.Surname} {src.Name}"))
.ReverseMap()
.ForMember(dist => dist.Surname, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[0]))
.ForMember(dist => dist.Name, opt => opt.MapFrom(src => src.CustomerName.Split(' ')[1]));
});
// mapping Customer -> CustomerDto
//...
//
// mapping CustomerDto -> Customer
var customerDto = new CustomerDto
{
CustomerName = "Shakhabov Adam",
Age = 31
};
var newCustomer = Mapper.Map<CustomerDto, Customer>(customerDto);
}
It is working.
Question
Do ForMember and ForPath the same things or when should I use ForPath() over ForMember()?

In this case, to avoid inconsistencies, ForPath is translated internally to ForMember. Although what #IvanStoev says makes sense, another way to look at it is that ForPath is a subset of ForMember. Because you can do more things in ForMember. So when you have a member, use ForMember and when you have a path, use ForPath :)

Related

How to provide additional parameter to Mapper's Map method?

I'm trying to map one list to another, but not everything I want is in the source object. Let me explain:
These are my two objects:
public class SourceItem
{
public int Id { get; set;}
public string Name { get; set;}
}
public class TargetItem
{
public int TargetId { get; set;}
public string TargetName { get; set;}
public int Foo { get; set; }
}
This is how I configure my Mapper:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<SourceItem, TargetItem>()
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
When I do this...
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
...the targets list's objects will have Foo set to 0. Obviously. What I'm trying to do is to also initialize Foo (with the parameter). Like this:
fooParameter = 321;
List<TargetItem> targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
foreach(var target in targets)
{
target.Foo = fooParameter;
}
Is it possible, to do it using Mapper?
Thanks in advance!
You can't do exactly what you want, but you can get pretty close by specifying mapping options when you call Map.
fooParameter = 321;
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems, opt =>
opt.AfterMap(
(src, dest) => {
foreach (var d in dest)
{
d.Foo = fooParameter;
}
})
);

Automapper before/after map callbacks during mapping

I'm using ASP.NET Core and Automapper 6.1.0 ,
I have two types that look like this
public class ExampleDTO
{
public Guid Id { get; set; }
public ProviderDTO Provider { get; set; }
}
public class Example
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
}
ProviderDTO class (which is irelevant in this case)
public class ProviderDTO
{
public Guid Id { get; set; }
public string Name { get; set; }
}
AutoMapper configuration looks like this:
CreateMap<Example, ExampleDTO>().ForMember(x => x.Provider, opt => opt.Ignore());
CreateMap<ExampleDTO, Example>().ForMember(dest => dest.Provider,
opt => opt.MapFrom(src => src.Provider.Id));
When I map from Example to ExampleDTO, I want to pass the value for ProviderDTO type.
I tried something like this.
_mapper.Map<ExampleDTO>(example, opt => opt.AfterMap((src, dest) => dest.Provider = myProvider));
I get this
'object' does not contain a defenition for 'Provider' and no extension method
Is this achievable? If yes, what am I doing wrong?
With the AutoMapper, you may need to provide both the source and destination type, such as:
_mapper.Map<Example, ExampleDTO>(example, opt => {
opt.AfterMap((src, dest) => dest.Provider = myProvider))
});

C# - Automapper - Map element with condition

I have two classes:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Quantity { get; set; }
};
public class Customer_
{
public string FirstNam { get; set; }
public string LastNam { get; set; }
public int Quantity { get; set; }
}
And a mapping between the two with Automapper library :
Customer[] data = new Customer[3];
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, Customer_>()
.ForMember(dest => dest.FirstNam, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.LastNam, opt => opt.MapFrom(src => src.LastName));
});
IMapper mapper = config.CreateMapper();
Customer_[] desti = mapper.Map<Customer[], Customer_[]>(data);
It works, but how to map only the elements of my array data that meet a condition ? For example : map only the element who have a Quantity > x
Thank's
Typically I'd do this before you map into AutoMapper:
Customer_[] desti = mapper.Map<Customer[], Customer_[]>(
data.Where(c => c.Quantity > 10).ToArray());
Don't try to put this sort of intelligence into AutoMapper, it's confusing and will lead to bugs.

Automapper projection and union

I have a problem with union and automapper projections.
I have two entities:
public class Entity
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
}
public class ExtendedEntity
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
public int SomeOtherProp { get; set; }
}
and projection:
public class EntityProjection
{
public DateTime ActionDate { get; set; }
public int SomeProp { get; set; }
public int SomeOtherProp { get; set; }
public string Source { get; set; }
}
i map entities to one projection, Entity does not have SomeOtherProp so i set 0 to it:
public class EntityProfile : Profile
{
protected override void Configure()
{
CreateMap<ExtendedEntity, EntityProjection>()
.ForMember(dest => dest.Source, opt => opt.MapFrom(src => "ext entity"));
CreateMap<Entity, EntityProjection>()
.ForMember(dest => dest.SomeOtherProp, opt => opt.MapFrom(src => 0))
.ForMember(dest => dest.Source, opt => opt.MapFrom(src => "entity"));
}
}
when i try to use next code i get error:
var entities = context.Set<Entity>()
.Project().To<EntityProjection>();
var extEntities = context.Set<ExtendedEntity>()
.Project().To<EntityProjection>();
var result = entities.Union(extEntities).OrderBy(p => p.ActionDate).ToList();
Error text: The type 'UserQuery+EntityProjection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be...
That means that properties in projection must be initialized in same order, how i can set projection properties initialization order by automapper?
Very late answer, and the short version seems to be "You can't".
I had exactly the same question (Can I force Automapper to initialise properties in a certain order?) and ended up mapping everything within a LINQ select statement.
For ease, I made it a static method within my DTO (cut-down code):
public static IQueryable<MyDto> QueryableFromTaskType1(
IQueryable<TaskType1> query)
{
return query.Select(src => new MyDto()
{
TaskId = src.Id,
AssetTypeName = src.Asset.AssetType.Name,
AssetId = src.Asset.Id,
AssetCode = src.Asset.Code,
AssetName = src.Asset.Name,
});
}
public static IQueryable<MyDto> QueryableFromTaskType2(
IQueryable<TaskType2> query)
{
return query.Select(src => new MyDto()
{
TaskId = src.Id,
AssetTypeName = src.AssetTypeName,
AssetId = src.AssetId,
AssetCode = src.AssetCode,
AssetName = src.AssetName,
});
}
then you can get your objects, as an IQueryable, simply pass them through the appropriate static method (which appends a select into the DTO - or projects as it's otherwise known) and then Union or Concat the resulting IQueryables.
The only downside is that Automapper will normally deal with recursive automapping, although I'm pretty certain that wouldn't map to SQL well anyway, so you probably don't lose much.

AutoMapper Conditional Entity Mapping

I have a db entity which stores the Order Addresses like this...
And I have the BLL classes like this...
public class DeliveryAddress
{
public string Id { get; set; }
public string PersonyName { get; set; }
public string CompanyName { get; set; }
public List<string> AddressLines { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string CountryCode { get; set; }
}
and another class like this...
public class InvoiceAddress
{
public string Id { get; set; }
public string PersonyName { get; set; }
public string CompanyName { get; set; }
public List<string> AddressLines { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string CountryCode { get; set; }
}
and I want to map the EF entity to the above classes on the basis of AddressType column. Can anybody explain me how to do that ?
UPDATE
I want to map to OR.DeliveryAddress if the addressType is "Delivery" and to OR.InvoiceAddress if the addressType is "Invoice"
So far, I have been able to do this, but I don't know how to apply condition on the entity mapping level...
Mapper.CreateMap<OrderAddress, OR.DeliveryAddress>()
.ForMember(d => d.City, o => o.MapFrom(s => s.city))
.ForMember(d => d.CompanyName, o => o.UseValue(string.Empty))
.ForMember(d => d.CountryCode, o => o.MapFrom(s => s.countryCode))
.ForMember(d => d.Id, o => o.MapFrom(s => s.id))
.ForMember(d => d.PersonyName, o => o.MapFrom(s => s.name))
.ForMember(d => d.Zip, o => o.MapFrom(s => s.zip));
UPDATE 2
After discussion with #Yuliam Here is the Fiddle that I could come up with for my problem...
You can create a customer mapper to object. And also you don't have to specify each property using ForMember because if the difference is only upper case / lower case (unless for PersonName), by default AutoMapper is case insensitive when mapping the property name.
Create a custom mapper to object.
public class AddressConverter : ITypeConverter<OrderAddress, object>
{
public object Convert(ResolutionContext context)
{
var o = context.SourceValue as OrderAddress;
if (o == null) return null;
if (o.addressType == "Delivery") return Mapper.Map<OR.DeliveryAddress>(o);
if (o.addressType == "Invoice") return Mapper.Map<OR.InvoiceAddress>(o);
return null;
}
}
Then define the mapper.
Mapper.CreateMap<OrderAddress, OR.DeliveryAddress>()
.ForMember(d => d.PersonyName, o => o.MapFrom(s => s.name));
Mapper.CreateMap<OrderAddress, OR.InvoiceAddress>()
.ForMember(d => d.PersonyName, o => o.MapFrom(s => s.name));
Mapper.CreateMap<OrderAddress, object>().ConvertUsing<AddressConverter>();
Usage.
var orderAddressDto = Mapper.Map<object>(orderAddress);
The actual orderAddressDto type will be based on the addressType. If you have an interface or base class for OR.DeliveryAddress and OR.InvoiceAddress that would be more strongly type. Then replace the object type with the interface / base class.
You may want to try taking a look at ResolveUsing
Semi-pseudo code, as I don't know what your entire Domain Model looks like:
Mapper.CreateMap<OrderObject, OrderDto>()
.ForMember(x => x.Address, opt => opt.ResolveUsing(oo => oo.Type == Invoice ? oo.InvoiceAddress : oo.DeliveryAddress));
I'm assuming here that you have an actual Order entity, which you're trying to make to an 'OrderDto' which only contains one address field.

Categories

Resources