AutoMapper to force null - c#

I use Automapper with EntityFramework Core, and have the following configuration for one of my objects (Couche, having associated an Affaire and a Sol object)
CreateMap<Couche, CoucheDTO>()
.ForMember(dto => dto.AffaireId, o => o.MapFrom(p => p.Affaire.Id))
.ForMember(dto => dto.SolId, o => o.MapFrom(p => p.Sol.Id))
.ForMember(dto => dto.SolNom, o => o.MapFrom(p => p.Sol.Nom))
.ForMember(dto => dto.SolCode, o => o.MapFrom(p => p.Sol.Code))
.ReverseMap()
.ForPath(couche => couche.Affaire, o => o.MapFrom(p => {return null; })) // ???
.ForPath(couche => couche.Sol, o => o.Ignore()); // ???
When I reverse map (from DTO to Business Object), I need only the correct AffaireId, the Affaire field should be set to null (I don't need to create invalid half-filled objects). How to do it with reverse map? Ignore seem do not put null....
PS. I tried
.ForPath(couche => couche.Affaire, o => o.MapFrom<AffaireDTO>(p => null))
.ForPath(couche => couche.Sol, o => o.MapFrom<AffaireDTO>(p => null));
but get then
InvalidOperationException: No coercion operator is defined between
types 'MyApp.Core.Entities.Sol' and 'MyApp.Core.Entities.Affaire'.
System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType
coercionType, Expression expression, Type convertToType)
DependencyResolutionException: An exception was thrown while
activating MyApp.Web.Services.SondageService -> λ:AutoMapper.IMapper
-> λ:AutoMapper.IConfigurationProvider. Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext
context, Action next)

There should be no need to use 'return'.
Try .ForPath(couche => couche.Affaire, o => o.MapFrom(p => null))

Finally, found solution to infere the type... (Affaire)null:
.ForPath(couche => couche.Affaire, o => o.MapFrom(p => (Affaire)null))
.ForPath(couche => couche.Sol, o => o.MapFrom(p => (Sol)null));

Related

Automapper - Filtering data after mapping

I have the following code:
CreateMap<DevicesTransferRequest, TransferRequestDto>()
.ForMember(d => d.SourceCarrierNames, o => o.MapFrom(s => s.DeviceMappings.Select(a => a.PreviousCarrier.Name).ToList()))
It works, but I want to remove duplicate values for SourceCarrierNames.
I try the following:
CreateMap<DevicesTransferRequest, TransferRequestDto>()
.ForMember(d => d.SourceCarrierNames, o => o.MapFrom(s => s.DeviceMappings.Select(a => a.PreviousCarrier.Name).ToList()))
.AfterMap((src, dest) => dest.SourceCarrierNames.Distinct())
;
but I see duplicate SourceCarrierNames inside every TransferRequestDto anyway.
Enumerable.Distinct Method
Returns: IEnumerable<TSource>
An IEnumerable that contains distinct elements from the source sequence.
Assign the result after distinct back to dest.SourceCarrierNames.
CreateMap<DevicesTransferRequest, TransferRequestDto>()
.ForMember(d => d.SourceCarrierNames, o => o.MapFrom(s => s.DeviceMappings.Select(a => a.PreviousCarrier.Name).ToList()))
.AfterMap((src, dest) =>
{
dest.SourceCarrierNames = dest.SourceCarrierNames
.Distinct()
.ToList();
});

Is there a way to map a field with automapper if the source field needs to be counted by a given condition?

I basically want to count the source field by a given condition. The plan is to count the navigation properties number (1 request can have multiple safechecks, so Safechecks is a virtual ICollection in the design), but separate them by its own Checktype field's value, like so:
MapperConfiguration config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<ListRequestsViewModel, Requests>().ReverseMap()
.ForMember(d => d.Nr_Sum_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Count))
.ForMember(d => d.Nr_Quality_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Where(x => x.CheckType.Equals("Qlty")).Count))
.ForMember(d => d.Nr_Quantity_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Where(x => x.CheckType.Equals("Qnty").Count));
}
);
The first "ForMember" row is legal, in case of the other two (Nr_Quality_SafeChecks and Nr_Quantity_SafeChecks), I got "Cannot convert lambda expression to type 'IValueResolver<Requests, ListRequestsViewModel, object>' because it is not a delegate type" error. So this is not working as it is.
How to resolve this?
You can use IEnumerable.Count().
.ForMember(d => d.Nr_Quality_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Count(x => x.CheckType.Equals("Qlty"))))
Got the solution.
s.SafeChecks.Where(...) clause returns an IEnumerable where .Count not supported. However, it can be called on an ICollection like List, so converting to list first should solve the problem.
MapperConfiguration config = new MapperConfiguration(
cfg =>
{
cfg.CreateMap<ListRequestsViewModel, Requests>().ReverseMap()
.ForMember(d => d.Nr_Sum_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Count))
.ForMember(d => d.Nr_Quality_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Where(x => x.CheckType.Equals("Qlty")).ToList().Count))
.ForMember(d => d.Nr_Quantity_SafeChecks, o => o.MapFrom(s => s.SafeChecks.Where(x => x.CheckType.Equals("Qnty")).ToList().Count));
}
);

Cache nested Property when mapping

I'm trying to map 2 objects, where the source one has a couple of Lazy loaded properties that go to DB any time they are used. I cannot change these objects. I can only change the mapping. Here is what I have so far.
CreateMap<CasePlan, CasePlanView>()
.ForMember(d => d.ProgramId, o => o.MapFrom(s => s.PrimaryReferral.ProgramRevisionId))
.ForMember(d => d.ClientName, o => o.MapFrom(s => s.PrimaryReferral.Client.FullName))
.ForMember(d => d.ClientId, o => o.MapFrom(s => s.PrimaryReferral.ClientId))
.ForMember(v => v.ClientBirthDate, o => o.MapFrom(s => s.PrimaryReferral.Client.BirthDate))
.ForMember(d => d.EnrollmentStartDate, o => o.MapFrom(s => s.PrimaryReferral.Enrollment.StartDate))
.ForMember(d => d.Age, o => o.MapFrom(s => s.PrimaryReferral.Client.BirthDate.ToAgeStringAtDate(s.Date).Replace("old", "")))
.ForMember(d => d.Program, o => o.MapFrom(s => s.PrimaryReferral.ProgramRevision.Program.Abbreviation))
.ForMember(d => d.PlacementWorker, o => o.MapFrom(s => s.PrimaryReferral.PlacementWorker.Name))
.ForMember(d => d.ReferralAgencyName, o => o.MapFrom(s => s.PrimaryReferral.ReferralSource.Name))
.ForMember(d => d.CourtStatus, o => o.MapFrom(s => s.PrimaryReferral.Client.LegalStatuses.FirstOrDefault() != null ? s.PrimaryReferral.Client.LegalStatuses.First().Status : null))
.ForMember(d => d.Approver, o => o.MapFrom(r => r.Approver.DisplayName))
.ForMember(d => d.ApproverId, o => o.MapFrom(s => s.ApproverId));
Everything gets mapped properly, but is very slow! Anytime I use the PrimarryReferral property a call to DB is made. Is there a way to instruct AutoMapper to cache the value and used it for all of the subsequent usages?
Would this work for you?
CreateMap<CasePlan, CasePlanView>()
.ConstructUsing((caseplan, b) => {
var client= a.PrimaryReferral.Client;
return new CasePlanView(){
ClientName= client.FullName,
ClientBirthDate= client.BirthDate //and so on
}
});
This way at least you will not access the database more than once for the properties of a.PrimaryReferral.Client.
However you would need to access the database for each object that has properties.
One way to fix this would be to eleminate the need for lazy loading using include at the source of CasePlan. I don't know if you are able to do that, but that would be my advice.
Another way, which I would not recommend, would be to Inject a DatabaseContext(or repository) to the mapper and by using the PK of the CasePlan, you get the CasePlan again from the database. (That essentially would not map from CasePlan object, but would re-get the object from the db. Therefore it would cause additional problems, if the data from the CasePlan object is not the same as in the db).

Automapper. Error when try to map Nested Type

I'm trying to map one object to another:
Mapper.CreateMap<ShippingAddressModel, ShippingAddress>()
.ForMember(x => x.Addresses.Name, opts => opts.MapFrom(x => x.Name));
But I have an error:
Expression 'x => x.Addresses.Name' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.
This should work:
Mapper.CreateMap<ShippingAddressModel, ShippingAddress>()
.ForMember(x => x.Addresses, opt => opt.MapFrom(model => model));
Mapper.CreateMap<ShippingAddressModel, Addresses>()
.ForMember(x => x.Name, opt => opt.MapFrom(model => model.Name));

AutoMapper.Mapper.Map sporadically returns object with null properties

I am using Automapper in my MVC/EF application. Occasionally when I launch my application to debug (F5), AutoMapper.Mapper.Map returns a mapped object that has null property values. I need to restart for this to resolve itself. This issue also occurs when I deploy my application to a remote server. As a workaround I have to check for null properties and then manually map my object.
ProductHierarchyRow mappedObj = _mapProvider.Map<VW_PROD_HIERARCHY,ProductHierarchyRow>(foundRow);
if (String.IsNullOrEmpty(mappedObj.DepartmentId)) //Test one of the properties
{
//Manually map because Automapper has failed for some reason
mappedObj = new ProductHierarchyRow();
mappedObj.MarketChannel = foundRow.MARKETCHANNEL;
mappedObj.BrandId = foundRow.BRAND_ID;
mappedObj.BrandLabel = foundRow.BRAND_LABEL;
mappedObj.DivisionId = foundRow.DIVISION_ID;
mappedObj.DivisionLabel = foundRow.DIVISION_LABEL;
mappedObj.DepartmentId = foundRow.DEPARTMENT_ID;
mappedObj.DepartmentLabel = foundRow.DEPARTMENT_LABEL;
mappedObj.ClassId = foundRow.CLASS_ID;
mappedObj.ClassLabel = foundRow.CLASS_LABEL;
mappedObj.SubclassId = foundRow.SUBCLASS_ID;
mappedObj.SubclassLabel = foundRow.SUBCLASS_LABEL;
}
Here is the mapping interface/implementation class:
public class MappingProvider : IMappingProvider
{
public MappingProvider()
{
AutoMapper.Mapper.CreateMap<VW_PROD_HIERARCHY, ProductHierarchyRow>().ForAllMembers(opt => opt.Ignore());
AutoMapper.Mapper.CreateMap<VW_PROD_HIERARCHY, ProductHierarchyRow>()
.ForMember(x => x.MarketChannel, o => o.ResolveUsing(s => s.MARKETCHANNEL))
.ForMember(x => x.BrandId, o => o.ResolveUsing(s => s.BRAND_ID))
.ForMember(x => x.BrandLabel, o => o.ResolveUsing(s => s.BRAND_LABEL))
.ForMember(x => x.DivisionId, o => o.ResolveUsing(s => s.DIVISION_ID))
.ForMember(x => x.DivisionLabel, o => o.ResolveUsing(s => s.DIVISION_LABEL))
.ForMember(x => x.DepartmentId, o => o.ResolveUsing(s => s.DEPARTMENT_ID))
.ForMember(x => x.DepartmentLabel, o => o.ResolveUsing(s => s.DEPARTMENT_LABEL))
.ForMember(x => x.ClassId, o => o.ResolveUsing(s => s.CLASS_ID))
.ForMember(x => x.ClassLabel, o => o.ResolveUsing(s => s.CLASS_LABEL))
.ForMember(x => x.SubclassId, o => o.ResolveUsing(s => s.SUBCLASS_ID))
.ForMember(x => x.SubclassLabel, o => o.ResolveUsing(s => s.SUBCLASS_LABEL));
}
public T1 Map<T, T1>(T entry)
{
return AutoMapper.Mapper.Map<T, T1>(entry);
}
public T1 Map<T, T1>(T source, T1 dest)
{
return (T1)AutoMapper.Mapper.Map(source, dest, typeof(T), typeof(T1));
}
}
Any ideas on why this is happening? Tips of how to troubleshoot? Thanks in advance!
Those CreateMaps should only be called once per AppDomain, at startup. Make sure those are called at App_Start. What's happening is you have competing threads calling CreateMap and Map at the same time.

Categories

Resources