Mapping into differnet types with Automapper - automapper

There are two classes in my Model:
public class Operation
{
public string Name { get; set; }
public Calculation Details { get; set; }
}
public class Calculation
{
public long Value { get; set; }
public List<decimal> Points { get; set; }
}
Mapping into this DTO:
public class OperationDto
{
public string Name { get; set; }
public CalculationDto Details { get; set; }
}
public class CalculationDto
{
public long Value { get; set; }
}
public class CalculationDetailedDto: CalculationDto
{
public List<decimal> Points { get; set; }
}
And sometimes the Client can request detailed information about the calculation. For example, depending of the command-line options:
class Program
{
static void Main(string[] args)
{
Mapper.CreateMap<Operation, OperationDto>();
Mapper.CreateMap<Calculation, CalculationDto>();
Mapper.CreateMap<Calculation, CalculationDetailedDto>();
var operation = new Operation
{
Name = "Very complicated opertion.",
Details =
new Calculation
{
Value = 1002,
Points = new List<decimal> {1.2m, 2.4m, 3.7m}
}
};
var operationDto = Mapper.Map<OperationDto>(operation);
Debug.WriteLine("Operation name: '{0}' value: '{1}'", operationDto.Name, operationDto.Details.Value);
if (args.Length > 0)
{
Debug.WriteLine("Details:");
foreach (var point in ((CalculationDetailedDto) operationDto.Details).Points)
{
Debug.WriteLine("{0}", point);
}
}
}
How do i tell Automapper at runtime map the calculation into CalculationDetailedDto?

You need to create another OperationDto that uses the detailed calculationDto, and create a map from Operation to OperationDetailedDto:
public class OperationDetailedDto
{
public string Name { get; set; }
public CalculationDetailedDto Details { get; set; }
}
Mapper.CreateMap<Operation, OperationDetailedDto>();
if (detailed) operationDto = Mapper.Map<Operation, OperationDto>(operation);
else operationDto = Mapper.Map<Operation, OperationDetailedDto(operation);

For those who do not fit solution proposed #fiskeboss can use the following.
You can re-map Calculation to CalculationDetailedDto after checking the command line arguments.
if (args.Length > 0)
{
operationDto.Details = Mapper.Map<CalculationDetailedDto>(operation.Details);
Debug.WriteLine("Details:");
foreach (var point in ((CalculationDetailedDto) operationDto.Details).Points)
{
Debug.WriteLine("{0}", point);
}
}

Related

How to get the current cache/document (Sales Order/Shipment) outside the context of a graph

I'm currently implementing a new carrier method and would like to access additional information on the Shipment/Sales Order object which is not passed through in the GetRateQuote & Ship functions of the implemented ICarrierService class.
The carrier method implements the ICarrierService interface and subsequently does not have access to a Graph where one would typically be able to access the current (cached?) document, etc.
How could I, for example, access the shipment number for which the Ship function is called?
My ultimate goal is to be able to generate a label for the shipment package, and in order to do so, I need to obtain the Shipment Number.
using PX.Api;
using PX.CarrierService;
using PX.CS.Contracts.Interfaces;
using PX.Data;
using PX.Data.Reports;
using PX.Objects.Common.Extensions;
using PX.Reports;
using PX.Reports.Data;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyCarriers.CollectCarrier
{
public class CollectCarrier : ICarrierService
{
private List<CarrierMethod> methods;
private List<string> attributes;
public IList<string> Attributes => (IList<string>)this.attributes;
public string CarrierID { get; set; }
public string Method { get; set; }
public ReadOnlyCollection<CarrierMethod> AvailableMethods => this.methods.AsReadOnly();
public CollectCarrier()
{
this.methods = new List<CarrierMethod>(1);
this.methods.Add(new CarrierMethod("CLT", "Collect"));
this.attributes = new List<string>(1);
}
[...]
public CarrierResult<ShipResult> Ship(CarrierRequest cr)
{
if (cr.Packages == null || cr.Packages.Count == 0)
{
throw new InvalidOperationException("CarrierRequest.Packages must be contain atleast one Package");
}
CarrierResult<ShipResult> carrierResult;
try
{
CarrierResult<RateQuote> rateQuote = this.GetRateQuote(cr, true);
ShipResult result = new ShipResult(rateQuote.Result);
//Report Parameters
Dictionary<String, String> parameters = new Dictionary<String, String>();
// ************************************************************************************
// At this point, I would like to be able to retrieve the current SOShipment's Shipment Number
// ************************************************************************************
parameters["shipmentNbr"] = "000009"; // Hard-coded this value to get the PDF generated.
//Report Processing
PX.Reports.Controls.Report _report = PXReportTools.LoadReport("SO645000", null);
PXReportTools.InitReportParameters(_report, parameters, SettingsProvider.Instance.Default);
ReportNode reportNode = ReportProcessor.ProcessReport(_report);
//Generation PDF
result.Image = PX.Reports.Mail.Message.GenerateReport(reportNode, ReportProcessor.FilterPdf).First();
result.Format = "pdf";
result.Data.Add(new PackageData(
cr.Packages.FirstOrDefault().RefNbr,
this.RandomString(6),
result.Image,
"pdf"
)
{
TrackingData = this.RandomString(6)
});
carrierResult = new CarrierResult<ShipResult>(result);
}
catch (Exception ex)
{
if (this.LogTrace)
{
this.WriteToLog(ex, this.GetType().Name + ".Ship().Exception");
}
List<Message> messageList = this.HandleException(ex);
messageList?.Insert(0, new Message("", "Failed to generate the collection label: "));
carrierResult = new CarrierResult<ShipResult>(false, null, (IList<Message>)messageList);
}
return carrierResult;
}
[...]
}
}
For reference, the CarrierRequest object that is passed to the functions contain the following information:
public class CarrierRequest
{
public string ThirdPartyAccountID
{
get;
set;
}
public string ThirdPartyPostalCode
{
get;
set;
}
public string ThirdPartyCountryCode
{
get;
set;
}
public IAddressBase Shipper
{
get;
set;
}
public IContactBase ShipperContact
{
get;
set;
}
public IAddressBase Origin
{
get;
set;
}
public IContactBase OriginContact
{
get;
set;
}
public IAddressBase Destination
{
get;
set;
}
public IContactBase DestinationContact
{
get;
set;
}
public IList<CarrierBox> Packages
{
get;
set;
}
public IList<CarrierBoxEx> PackagesEx
{
get;
set;
}
public IList<string> Methods
{
get;
set;
}
public DateTime ShipDate
{
get;
set;
}
public UnitsType Units
{
get;
private set;
}
public bool SaturdayDelivery
{
get;
set;
}
public bool Resedential
{
get;
set;
}
public bool Insurance
{
get;
set;
}
public string CuryID
{
get;
private set;
}
public IList<string> Attributes
{
get;
set;
}
public decimal InvoiceLineTotal
{
get;
set;
}
public string FreightClass
{
get;
set;
}
public bool SkipAddressVerification
{
get;
set;
}
public IList<ISETerritoriesMappingBase> TerritoriesMapping
{
get;
set;
}
public CarrierRequest(UnitsType units, string curyID)
{
if (string.IsNullOrEmpty(curyID))
{
throw new ArgumentNullException("curyID");
}
Units = units;
CuryID = curyID;
}
}
I have seen a similar question here on SO, but I'm not entirely sure that is applicable to my specific request?
Any assistance will be highly appreciated.
See below as an option to loop through your currents and search for the specific current object:
SOShipment ship = null;
for (int i = 0; i < Caches.Currents.Length; i++)
{
if (Caches.Currents[i].GetType() == typeof(SOShipment))
{
ship = (SOShipment)Caches.Currents[i];
break;
}
}

AutoMapper .ReverseMap() .Ignore() not working

Having an issue with version 6.1.1. In the below, the result of the reverse map still has the Company object populated. Per this post, which shows what I am doing below, except they are ignoring a property, and I'm ignoring a complex object.
What am I missing?
CreateMap<Item, ItemViewModel>(MemberList.Destination)
.ReverseMap()
.ForMember(x => x.Company, x => x.Ignore())
;
With AutoMapper 6.1 you could use ForPath instead ForMember to ignore complex objects.
See How to ignore property with ReverseMap for further information.
I see not what is wrong, but here is a running sample:
namespace AutomapperTest2
{
internal class Program
{
#region Methods
private static void Main(string[] args)
{
// Configure the mappings
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ApplicantEducation, ApplicantEducationVM>();
cfg.CreateMap<Applicant, ApplicantVM>().ReverseMap()
.ForMember(x => x.Education, x => x.Ignore());
});
var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
var mapper = config.CreateMapper();
Applicant ap = new Applicant
{
Name = "its me",
Education =
new ApplicantEducation
{
SomeInt = 10,
SomeString = "sampleString"
}
};
// Map
ApplicantVM apVm = Mapper.Map<Applicant, ApplicantVM>(ap);
Applicant apBack = Mapper.Map<ApplicantVM, Applicant>(apVm);
}
#endregion
}
/// Your source classes
public class Applicant
{
public ApplicantEducation Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducation
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
// Your VM classes
public class ApplicantVM
{
public string Description { get; set; }
public ApplicantEducationVM Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducationVM
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
}
}

SchemaBuilder with complex types

I want to store complex content part record but couldn't create columns with SchemaBuilder in Migrations file.
Here are my classes:
public enum BoxInheritance
{
Empty, Inherit, Enter
}
public class BoxSize
{
public string Width { get; set; }
public string Height { get; set; }
}
public class BoxSpace
{
public string Left { get; set; }
public string Right { get; set; }
public string Top { get; set; }
public string Bottom { get; set; }
}
public class BoxPartRecord : ContentPartRecord
{
public virtual BoxSize Size { get; set; }
public virtual BoxSpace Space { get; set; }
public virtual Dictionary<string, BoxInheritance> Inheritances { get; set; }
public BoxPartRecord()
{
Size = new BoxSize();
Space = new BoxSpace();
Inheritances = new Dictionary<string, BoxInheritance>();
}
}
Is it ok to use a content part record like this?
How to create a table for this content part record?
I think this won't work. My suggestion is to use simple types in the record class and complex types in the content part itself (you can do the mapping there).
public class BoxPartRecord
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
...
}
public class BoxPart : ContentPart
{
public BoxSize Size { get { return new BoxSize {record.Width, record.Height} ...
}

Complex Automapper Configuration

I'm mapping from an existing database to a DTO and back again use Automapper (4.1.1) and I've hit a few small problems.
I have a (simplified) model for the database table:
public class USER_DETAILS
{
[Key]
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
and a DTO object:
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
I've created a profile
public class FromProfile : Profile
{
protected override void Configure()
{
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
this.SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
this.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
this.CreateMap<USER_DETAILS, User>();
}
}
However, it seems that Automapper doesn't like combining this many settings in the config. Even simplifying the models, I can't get
this.RecognizePrefixes("UDT_");
this.ReplaceMemberName("CCY", "Currency");
to work together, and whilst
this.CreateMap<decimal, double>().ConvertUsing((decimal src) => (double)src);
works ok with the models in the test, it fails when using it against a database.
Is there a way to get all this to work together, or should I fall back to using ForMember(). I was really hoping I could get this working as there are a lot of tables in this system, and I'd rather not have to do each one individually.
You will need to extend this for other types, only tested with strings, I have an extension method that does all the work and looks for unmapped properties.
public class USER_DETAILS
{
public string UDT_LOGIN { get; set; }
public string UDT_USER_NAME { get; set; }
public string UDT_INITIALS { get; set; }
public string UDT_USER_GROUP { get; set; }
// public decimal UDT_CLAIM_LIMIT { get; set; }
public string UDT_CLAIM_CCY { get; set; }
}
public class User
{
public string Login { get; set; }
public string UserName { get; set; }
public string Initials { get; set; }
public string UserGroup { get; set; }
//public double ClaimLimit { get; set; }
public string ClaimCurrency { get; set; }
}
public static class AutoMapperExtensions
{
public static IMappingExpression<TSource, TDestination>
CustomPropertyMapper<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
var properties = sourceType.GetProperties();
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
var similarPropertyName =
properties.FirstOrDefault(x => x.Name.Replace("_", "").Replace("UDT", "").ToLower().Contains(property.ToLower()));
if(similarPropertyName == null)
continue;
var myPropInfo = sourceType.GetProperty(similarPropertyName.Name);
expression.ForMember(property, opt => opt.MapFrom<string>(myPropInfo.Name));
}
return expression;
}
}
class Program
{
static void Main(string[] args)
{
InitializeAutomapper();
var userDetails = new USER_DETAILS
{
UDT_LOGIN = "Labi-Login",
UDT_USER_NAME = "Labi-UserName",
UDT_INITIALS = "L"
};
var mapped = Mapper.Map<User>(userDetails);
}
static void InitializeAutomapper()
{
Mapper.CreateMap<USER_DETAILS, User>().CustomPropertyMapper();
}
}
}

Complex collections with Automapper

Here is what I have where I hope someone can help us out:
class Source
{
string name { get; set; }
Inner { get; set; }
}
class Inner
{
Col A { get; set; }
Col B { get; set; }
}
class Col : IList<ClassX>, IEnunmerable<ClassX>
I need to map class Source to a destination type which has:
class Dest
{
string name { get; set; }
IList<ClassY> A { get; set;}
IList<ClassY> B { get; set;}
}
Now, ClassX and class ClassY share the same properties. ClassY class has a subset of the ClassX primitive properties with the exact same names and types.
Tried all kinds of mappings. Just the ClassX to ClassY map, with the collections, without and with any mapping get no mapping found between or missing configuration between Source and Dest
AutoMapper.Mapper.Map<Source, Dest>(src);
Can someone help me out with the mapping? Thanks in advance.
This question is a few months old, but if you're still looking for an answer, this is what I tried that worked:
class Source
{
public string Name { get; set; }
public Inner Inner { get; set; }
}
class Inner
{
public Col A { get; set; }
public Col B { get; set; }
}
class Col : List<ClassX> { }
class ClassX
{
public int Index { get; set; }
public string Name { get; set; }
public ClassX() : this(0, "") { }
public ClassX(int index, string name)
{
this.Index = index;
this.Name = name;
}
}
class ClassY
{
public int Index { get; set; }
public string Name { get; set; }
public ClassY() : this(0, "") { }
public ClassY(int index, string name)
{
this.Index = index;
this.Name = name;
}
}
class Dest
{
public string Name { get; set; }
public List<ClassY> A { get; set; }
public List<ClassY> B { get; set; }
}
[TestMethod]
public void ComplexTest()
{
Mapper.CreateMap<Source, Dest>()
.ForMember(dest => dest.A, config => config.MapFrom(src => src.Inner.A))
.ForMember(dest => dest.B, config => config.MapFrom(src => src.Inner.B));
Mapper.CreateMap<ClassX, ClassY>();
Source source = new Source
{
Name = "Source",
Inner = new Inner
{
A = new Col
{
new ClassX(1, "First"),
new ClassX(2, "Second"),
new ClassX(3, "Third"),
new ClassX(4, "Fourth"),
},
B = new Col
{
new ClassX(5, "Fifth"),
new ClassX(6, "Sixth"),
new ClassX(7, "Seventh"),
new ClassX(8, "Eighth"),
},
}
};
Dest destination = Mapper.Map<Source, Dest>(source);
Assert.AreEqual(source.Name, destination.Name);
Assert.AreEqual(source.Inner.A.Count, destination.A.Count);
Assert.AreEqual(source.Inner.B.Count, destination.B.Count);
Assert.AreEqual(source.Inner.A[0].Name, destination.A[0].Name);
Assert.AreEqual(source.Inner.B[0].Name, destination.B[0].Name);
}
I didn't go too in-depth with my Asserts, so there may be something I missed, but they appear to be mapped properly.

Resources