Complex Object to List Mapping in automapper - RESTAPI - c#

Below is my Source object
public class Source
{
public string Name { get; set; }
public DateTime GeneratedFrom { get; set; }
public DateTime GeneratedTo { get; set; }
}
Then the Destination object
public class Destination
{
public DateTime Day{ get; set; }
public string Name { get; set; }
}
The Date in the destination gets its value from Source.GenerateFrom and Source.GenerateTo (iterating until Source.Generatefrom <= Source.GenerateTousing while loop). Finally, I need a list of objects as below.
{"Day":"2019-02-04","name":"abc"},
{"Day":"2019-02-05","name":"abc"},
{"Day":"2019-02-06","name":"abc"}
Can this be achieved using automapper?
Below is the mapper profile class with the whileloop. Not sure if this is the right approach
CreateMap<Source, Destination>()
.forMember(
dest => dest.Day,
opt => opt.MapFrom(src => src.GenerateFrom))
.AfterMap((src, dest) =>
{
var StartDate = src.GenerateFrom;
var EndDate = src.GenerateTo;
List<Destination> list = new List<Destination>();
while(Startdate <= EndDate)
{
var data = new Destination {Day = StartDate, name = src.Name};
list.Add(data);
StartDate.AddDays(1);
}
});

You can register this mapping for Your objects, if Day always get it's value from GeneratedFrom property. (Or modify it according to Your needs)
CreateMap<Source, Destination>()
.ForMember(d => d.GeneratedFrom, o => o.MapFrom(s => s.Day))
.ForMember(d => d.Name, o => o.MapFrom(s => s.Name));
Then If You want to get a list of objects where Source.Generatefrom <= Source.GenerateTo You need to use System.Linq expression:
var destList = sourceList.Where(x => x.GeneratedFrom <= x.GeneratedTo)
.Select(_mapper.Map<Destination>).ToList();
Then serialise it using Newtonsoft.Json library:
var json = JsonConvert.SerializeObject(destList);

Related

How apply condition for multiple properties

Hello I am trying to use AutoMapper for a mapping. Let's imagine I have a domain class like that and a DTO one with the exact same structure.
public class Address
{
public int State { get; set; }
public string StateCode { get; set; }
public int City { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Phone { get; set; }
public Address Address { get; set; }
...
}
I want to construct a mapping that allows me only set the properties Phone and Address (and others, but for simplicity let's assume is only these two) conditionally depending on the value of the property age. Moreover, if the condition of the age property is met I want conditionally set the property StateCode only if the property State is not null. The logic is described below.
var personDto = new personDto();
personDto.Id = person.Id;
personDto.Name = person.Name;
personDto.Age = person.Age;
if(person.Age > 18)
{
personDto.Phone = person.Phone;
personDto.Address = new AddressDto();
personDto.Address.State = person.Address.State;
if (person.Address.State != null)
{
personDto.Address.StateCode = person.Address.StateCode;
}
personDto.Address.Street = person.Address.Street;
personDto.Address.ZipCode = person.Address.ZipCode;
}
I am trying to apply the same logic using Auto mapper, but I did not find in the documentation a way to do this.
CreateMap<Person, PersonDto>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Id))
.ForMember(d => d.Age , o => o.MapFrom(s => s.Age))
.ForMember(d => d.Name , o => o.MapFrom(s => s.Name ))
.ForMember(d => d.Phone , o =>
{
o.Condition(s => s.Age > 18); // I do not want repeat same condition for every property
o.MapFrom(s => s.Phone );
})
.ForMember(d => d.Address, o =>
{
o.Condition(s => s.Age > 18);
o.MapFrom(s => s.Address); // how can I nest the if condition of the State property here?
})
...
How can I apply the same nested logic here on Auto mapper? The whole point is how I can configure the mapping of several properties respecting the same condition and configure the nested condition as shown previously.

Automapper create destination type object with no values inside

i have come across a similar problem as mentioned in this question AutoMapper setting destination object to null
my mapper is creating an empty object of destination type although i can see values in my source object in debugger.
my source object is a grpc message of submitOrder type
syntax = "proto3";
option csharp_namespace = "SubmitOrderService";
package tracksubmitorder;
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
service SubmitOrderService {
rpc SubmitOrder(submitOrder) returns (google.protobuf.Empty);
}
// The request message containing the user's name.
message submitOrder {
int64 orderID= 1;
int32 beef =2;
int32 chicken =3;
int32 tofu =4;
int32 shrimp =5;
google.protobuf.Timestamp createdOn = 20;
string comment=30;
}
my destination is a data model
namespace SubmitOrderService.Models
{
public class Submit_Order
{
public long Id { get; set; }
public string Comments { get; set; }
public DateTime CreatedAt { get; set; }
public int CountBeef { get; set; }
public int CountChicken { get; set; }
public int CountTofu { get; set; }
public int CountShrimp { get; set; }
}
}
this is my profile file
using AutoMapper;
using SubmitOrderService.Models;
namespace SubmitOrderService
{
public class SubmitOrderProfile: Profile
{
public SubmitOrderProfile()
{
CreateMap<submitOrder, Submit_Order>().ForMember(
dest => dest.Comments,
opt => opt.MapFrom(src => $"{src.Comment}")
).ForMember(
dest => dest.CreatedAt,
opt => opt.MapFrom(src => $"{src.CreatedOn }")
)
.ForMember(
dest => dest.CountChicken ,
opt => opt.MapFrom(src => $"{src.Chicken}")
)
.ForMember(
dest => dest.CountBeef ,
opt => opt.MapFrom(src => $"{src.Beef}")
)
.ForMember(
dest => dest.CountShrimp ,
opt => opt.MapFrom(src => $"{src.Shrimp}")
)
.ForMember(
dest => dest.CountTofu ,
opt => opt.MapFrom(src => $"{src.Tofu}")
).ReverseMap();
}
}
}
this is my line for mapping where reqOrder is of type submitOrder
var order1 = _mapper.Map<Submit_Order>(reqOrder);
ps. This is my first time trying out Automapper, so any kind of help will be appreciated.
solved the problem. I had to explicitly convert google.protobuf.Timestamp to DateTime for my CreatedAt member. like this,
ForMember( dest => dest.CreatedAt, opt => opt.MapFrom(src => src.CreatedOn.ToDateTime()) )

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;
}
})
);

How to resolve source member for multi-property mapping in AutoMapper?

I have DO and DTO classes:
public class DO
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
public class DTO
{
public int MappedProperty1 { get; set; }
public int MappedProperty2 { get; set; }
}
The mapping registration for the above classes are like below.
// From DO to DTO:
configuration.CreateMap<DO, DTO>()
.ForMember(d => d.MappedProperty1, o => o.MapFrom(s => s.Property1))
.ForMember(d => d.MappedProperty2, o => o.MapFrom(s => s.Property1 + s.Property2));
// From DTO to DO:
configuration.CreateMap<DTO, DO>()
.ForMember(d => d.Property1, o => o.MapFrom(s => s.MappedProperty1))
.ForMember(d => d.Property2, o => o.MapFrom(s => s.MappedProperty1 - s.MappedProperty2));
Elsewhere in the code I need to get the destination property names in the mapping. I'm currently doing this by:
var map= mapper.ConfigurationProvider.FindTypeMapFor<DTO, DO>();
var property = map.GetPropertyMaps().First(f => f.SourceMember == typeof(DTO).GetProperty("MappedProperty1"));
var name = property.DestinationProperty.Name;
This works fine with MappedProperty1. However, it does not work with MappedProperty2 as there are two properties participating in the mapping. Instead, I get the error message:
"Sequence contains no matching element".
I also tried with the following instead of First filter and in debug I can see that the count in the list is zero.
var map= mapper.ConfigurationProvider.FindTypeMapFor<DTO, DO>();
var property = map.GetPropertyMaps().Where(w => w.SourceMember == typeof(DTO).GetProperty("MappedProperty2")).ToList();
How can I achieve this source name resolution for multi-property mapping?

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