automapper mapping certain bool property based on enum value - c#

The destination class has a list of bools. The DTO that gets mapped to the destination class has 1 enum property. depending on what the enum is some of the bools in the destination class should be set. how to achieve it in automapper?
.ForMember() won't work because i would have to do enum logic check for each of the bool property.
I want to do a mapping this.CreateMap<DestinationDTO, Destination>() where depending on what the payout is Property1 or Property2 or Property3 gets set.
See below:
public class Destination
{
public bool? Property1{get; set;}
public bool? Property2{get; set;}
public bool? Property3{get;set;}
}
public class DestinationDTO
{
public Enum Payout{get; set;}
}
public Enum Payout
{
Proration = 1,
Recurrent = 2,
Lumpsum = 3
}
If the DestinationDTO.Payout == Payout.Proration, I want to set Destination entity class's Property1 to be true, similarly depending on what payout it is, I might want to set another Property in the entity class. Can I do this in automapper when mapping the DestinationDTO to Destination entity class?

You can do this by using ForMember expressions:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<DestinationDTO, Destination>()
.ForMember(d => d.Property1,
m => m.MapFrom(d => d.Payout == Payout.Proration ? true : default(bool?)))
.ForMember(d => d.Property2,
m => m.MapFrom(d => d.Payout == Payout.Recurrent ? true : default(bool?)))
.ForMember(d => d.Property3,
m => m.MapFrom(d => d.Payout == Payout.Lumpsum ? true : default(bool?)));
});
var mapper = config.CreateMapper();
var dtos = new[]
{
new DestinationDTO { Payout = Payout.Proration },
new DestinationDTO { Payout = Payout.Recurrent },
new DestinationDTO { Payout = Payout.Lumpsum },
};
var destinations = dtos.Select(d => mapper.Map<Destination>(d));
Off-topic: I'd prefer non-nullable booleans. Then you can remove the ? true : default(bool?) parts and a Destination still tells the truth in all of its properties.

Related

AutoMapper does not ignore List when mapping from source to existing destination

I'm having a small problem with AutoMapper. I have isolated the issue I am facing, if it actually is an issue and not just a misunderstanding.
Here are the classes I am working with:
public class DemoEntity
{
public List<string> Items { get; set; }
public string Name { get; set; }
}
public class DemoDto
{
public List<string> Items { get; set; }
public string Name { get; set; }
}
public class DemoProfile : Profile
{
public DemoProfile()
{
CreateMap<DemoDto, DemoEntity>()
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
}
}
In the Dependency Injection part (which seems to be in Program.cs for .NET 6, but is in Startup.cs in my main project), I have this code which I have read that should help allow nullable collections:
builder.Services.AddAutoMapper(configAction => { configAction.AllowNullCollections = true; }, typeof(Program));
Here is my test-code:
var dto = new DemoDto();
var entity = new DemoEntity()
{
Items = new List<string>() { "Some existing item" },
Name = "Existing name"
};
// Works as expected
var newEntity = _mapper.Map<DemoEntity>(dto);
// Sets the entity.Items to an empty list
_mapper.Map(dto, entity);
As you can see in the DemoProfile constructor, I set a condition to only map if srcMember != null, which works for the Name property. With the AllowNullCollections in the service registration, I can map to a new object with a null list (would be an empty list without the AllowNullCollections part).
My expected outcome would be AutoMapper to see that dto.Items is null, and not touch the entity.Items property during the mapping, and leave it with 1 string in the list. The actual outcome is that entity.Items is a list with 0 items. Name property is ignored.
Am I missing something? How can I adjust my code to work so that AutoMapper ignores a list that is null during mapping on an existing destination?
You can look for PreCondition to prevent the mapping from source when the source's member (with the array, List) is null or empty.
CreateMap<DemoDto, DemoEntity>()
.ForMember(dest => dest.Items, opt => opt.PreCondition((source, dest) =>
{
return source.Items != null && source.Items.Count > 0;
}))
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
Sample demo on .NET Fiddle

AutoMapper - Map child property hiding base class property

I'm encountering a quite stupid issue while trying to map a class to a derived class using AutoMapper with C#.
These are my classes:
class BaseParent {
public string Name { get; set; }
public BaseChild Child { get; set; }
}
class BaseChild {
public int Age { get; set; }
}
class DerivedParent : BaseParent {
public new DerivedChild Child { get; set; }
}
class DerivedChild : BaseChild { }
In particular, what I'm trying to achieve is that all the properties of the mapped class are correctly set. The issue is that the Child property of the mapped class is not set and remains null.
This is the mapping configuration I'm using:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<BaseChild, DerivedChild>();
cfg.CreateMap<BaseParent, DerivedParent>()
.ForMember(dest => dest.Child, opt => opt.MapFrom(src => src.Child));
});
Any help is appreciated.
Thanks
EDIT
Actually is not correct to say that Child property remains null.
Debugging the code I can see that there are 2 child properties with the same name because of the new modifier used to hide the parent one.
Anyway, the property I need is still null.
Are you sure you're looking at it correctly?
Given the following config:
// Arrange
var mapper = new Mapper(new MapperConfiguration(cfg => {
cfg.CreateMap<BaseChild, DerivedChild>();
cfg.CreateMap<BaseParent, DerivedParent>()
.ForMember(dest => dest.Child, opt => opt.MapFrom(src => src.Child));
}));
var baseParent = new BaseParent { Name = "A", Child = new BaseChild { Age = 1 } };
// Act
var derived = mapper.Map<DerivedParent>(baseParent);
I can assure you that:
derived.Child is certainly not null
It is of type DerivedChild
The hidden BaseChild is null
Which you can see in the following (see working Fiddle):
// Assert
Assert.IsNotNull(derived);
Assert.IsInstanceOf<DerivedParent>(derived);
// The 'new' Child property is not null
Assert.IsNotNull(derived.Child);
Assert.IsInstanceOf<DerivedChild>(derived.Child);
// The hidden property should be null
Assert.IsNull(((BaseParent)derived).Child);

C# Automapper inner list from another inner list of different type

I am trying to map something similar to this model:
class Source {
...
SubSource subSource;
}
class SubSource {
...
List<SourceListItem> list;
SomeInfo someInfo;
...
}
class SomeInfo {
string name;
...
}
class SourceModel {
...
SomeInfoModel someInfoModel;
...
}
class SomeInfoModel {
string name;
List<SourceListItemModel> list;
...
}
What I need is to map "SubSource.List" to "SomeInfoModel.List". I am able to map every other property correctly, but the list is always empty after the mapping and no errors happen during execution.
I have the following configuration for the mappings:
CreateMap<SourceListItem, SourceListItemModel>()
CreateMap<SomeInfo, SomeInfoModel>()
CreateMap<Source, SourceModel>()
...
.ForPath(dest => dest.someInfoModel.list, opt => opt.MapFrom(src =>
src.subSource.list))
...
Executing your code, the call to configuration.AssertConfigurationIsValid(); throws an exception where is clearly described the issue you are running into
Unmapped members were found. Review the types and members below. Add a
custom mapping expression, ignore, add a custom resolver, or modify
the source/destination type.
For no matching constructor, add a no-arg ctor, add optional
arguments, or map all of the constructor parameters
SomeInfo -> SomeInfoModel (Destination member list) SomeInfo ->
SomeInfoModel (Destination member list)
Unmapped properties: list
You can refer to the documentation
Tried to reproduce, but for me the mapping of list property works.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceListItem, SourceListItemModel>();
cfg.CreateMap<SomeInfo, SomeInfoModel>();
cfg.CreateMap<Source, SourceModel>()
.ForPath(dest => dest.someInfoModel.list, opt => opt.MapFrom(src =>
src.subSource.list));
});
var source = new Source()
{
subSource = new SubSource()
{
list = new List<SourceListItem>()
{
new SourceListItem() { Text1 = "text1" },
new SourceListItem() { Text1 = "text2" },
}
}
};
var mapper = new Mapper(config);
var model = mapper.Map<SourceModel>(source);
The model used:
public class SourceListItem
{
public string Text1 { get; set; }
}
public class SourceListItemModel
{
public string Text1 { get; set; }
}

Entity Framework Core 2.1 - owned types and nested value objects

I'm learning DDD and the tutorial I'm currently following is implemented using NHibernate, but since my lack of experience with it I've decided to go through the course using EF Core 2.1.
However, I'm currently a bit stuck with the following: I have three classes Customer which is an entity and two value objects (CustomerStatus and inside of it value object ExpirationDate) - like this:
public class Customer : Entity
{
//... constructor, other properties and behavior omitted for the sake of simplicity
public CustomerStatus Status { get; set; }
}
public class CustomerStatus : ValueObject<CustomerStatus>
{
// customer status is enum containing two values (Regular,Advanced)
public CustomerStatusType Type { get; }
public ExpirationDate ExpirationDate { get; }
}
public class ExpirationDate : ValueObject<ExpirationDate>
{
//... constructor, other properties and behavior omitted for the sake of simplicity
public DateTime? Date { get; private set; }
}
When I try to do the following inside of my DbContext:
modelBuilder.Entity<Customer>(table =>
{
table.OwnsOne(x => x.Status,
name =>
{
name.Property(x => x.ExpirationDate.Date).HasColumnName("StatusExpirationDate");
name.Property(x => x.Type).HasColumnName("Status");
});
});
I'm getting the following error:
The expression 'x => x.ExpirationDate.Date' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'.
Parameter name: propertyAccessExpression'
Beside this I've tried doing things like:
table.OwnsOne(x => x.Status.ExpirationDate,
name =>
{
name.Property(x => x.Date).HasColumnName("StatusExpirationDate");
});
table.OwnsOne(x => x.Status,
name =>
{
name.Property(x => x.Type).HasColumnName("Status");
});
But it also leads to:
The expression 'x => x.Status.ExpirationDate' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'.
I've also tried:
modelBuilder.Entity<Customer>()
.OwnsOne(p => p.Status,
cb => cb.OwnsOne(c => c.ExpirationDate));
But with no luck as well... Anyways, any help would be greatly appreciated, also if possible it would be really great if someone could explain why none of my tries are not working? Thanks in advance!
UPDATE
After doing as stated in Ivan's comment first I was getting error about CustomerStatus class constructor so I've added default protected one.
After that I started getting error:
Field 'k__BackingField' of entity type 'CustomerStatus' is readonly and so cannot be set.
Here's inner of my CustomerStatus class if it helps:
public class CustomerStatus : ValueObject<CustomerStatus>
{
public CustomerStatusType Type { get; }
public ExpirationDate ExpirationDate { get; }
public static readonly CustomerStatus Regular =
new CustomerStatus(CustomerStatusType.Regular, ExpirationDate.Infinite);
public bool IsAdvanced => Type == CustomerStatusType.Advanced && !ExpirationDate.IsExpired;
private CustomerStatus(CustomerStatusType type, ExpirationDate expirationDate)
{
Type = type;
ExpirationDate = expirationDate;
}
protected CustomerStatus()
{
}
public static CustomerStatus Create(CustomerStatusType type, ExpirationDate expirationDate)
{
return new CustomerStatus(type, expirationDate);
}
public CustomerStatus Promote()
{
return new CustomerStatus(CustomerStatusType.Advanced, ExpirationDate.Create(DateTime.UtcNow.AddYears(1)).Value);
}
protected override bool EqualsCore(CustomerStatus other)
{
return Type == other.Type && ExpirationDate == other.ExpirationDate;
}
protected override int GetHashCodeCore()
{
return Type.GetHashCode() ^ ExpirationDate.GetHashCode();
}
}
UPDATE
All it took was adding private setters on Type and ExpirationDate properties inside of CustomerStatus class and in combination with Ivan's answer it works like a charm. Thanks a lot!
Your attempts are not working because owned types can only be configured through their owner entity, and more specifically, through their own builder returned by the OwnsOne method or provided as an argument of the Action<T> argument of the OwnsOne method of the owner entity builder.
So the configuration should be something like this (note the nested OwnsOne):
modelBuilder.Entity<Customer>(customer =>
{
customer.OwnsOne(e => e.Status, status =>
{
status.Property(e => e.Type).HasColumnName("Status");
status.OwnsOne(e => e.ExpirationDate, expirationDate =>
{
expirationDate.Property(e => e.Date).HasColumnName("StatusExpirationDate");
});
});
});

Find out mapped property using AutoMapper

I'm looking to 'map' errors when persisting DTOs back to the property in a viewmodel corresponding to the DTO property that caused the error, PersonDto.PreferedName causes a DB error, say Cannot be null, and this property maps to the viemodel PersonViewModel.Name, I want to be able to display an error message in the langiage of the view, e.g. "Name is required".
Is there any way I can query Mapper or some other AutoMapper object to find which viewmodel property PreferedName maps to?
It looks like you are getting the errors from the ModelState but to answer the original question you can use the following
using AutoMapper;
using System;
using System.Linq;
namespace ConsoleApplicationAutoMapper
{
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.DtoMyProperty, x => x.MapFrom(y => y.MyProperty)
));
Order order = new Order() { MyProperty = 12 };
var typeMaps = config.CreateMapper()
.ConfigurationProvider
.GetAllTypeMaps()
.Where(t => t.SourceType == typeof(Order) && t.DestinationType == typeof(OrderDto))
.Single()
.GetPropertyMaps();
foreach (var map in typeMaps)
Console.WriteLine(map.SourceMember.Name + "->" + map.DestinationProperty.Name);
Console.ReadLine();
}
}
public class Order
{
public int MyProperty { get; set; }
}
public class OrderDto
{
public int DtoMyProperty { get; set; }
}
}
As per comments, you can annotate your viewmodel with [Required] (System.ComponentModel.DataAnnotations.RequiredAttribute) and check in your controller action for ModelState.IsValid.
Serializing the contents of ModelState.Values and returning to your WPF front end will allow you to display any validation errors in whatever way suits.

Categories