I'm trying to Map
and ReverseMap
an ImmutableHashSet
property to an ICollection
using AutoMapper
.
The Automapper successfully maps the ImmutableHashSet
property to ICollection
but it fails to map the ICollection
back to ImmutableHashSet
Here is the minimal reproducible example:
Consider I have Order
and OrderItem
class as below:
public class Order
{
private HashSet<OrderItem> _orderItems;
public Order()
{
_orderItems = new HashSet<OrderItem>();
}
public ImmutableHashSet<OrderItem> OrderItems
{
get => _orderItems.ToImmutableHashSet();
private set => _orderItems = value.ToHashSet();
}
public void AddOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
public void RemoveOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
}
public class OrderItem
{
public OrderItem(int orderId, string orderName)
{
OrderId = orderId;
OrderName = orderName;
}
public int OrderId { get; private set; }
public string OrderName { get; private set; }
}
The Order
and OrderItem
classes need to be mapped to below OrderDto
and classes
public class OrderDto
{
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItemDto
{
public int OrderId { get; set; }
public string OrderName { get; set; }
}
The OrderProfile
class below defines the Automapper Profile to map Order
to OrderDto
and visa-versa.
public class OrderProfile : Profile
{
public OrderProfile()
{
CreateMap<Order, OrderDto>()
.ReverseMap();
CreateMap<OrderItem, OrderItemDto>()
.ReverseMap();
}
}
public class OrderItemDto
{
public int OrderId { get; set; }
public string OrderName { get; set; }
}
Code to map and reverse map the Order
and OrderDto
classes:
private static void Map()
{
var mapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new OrderProfile());
}).CreateMapper();
var order = new Order();
order.AddOrderItem(new OrderItem(1, "Laptop"));
order.AddOrderItem(new OrderItem(2, "Keyboard"));
// This code maps correctly
var orderDto = mapper.Map<OrderDto>(order);
// This is where I get an exception
var orderMappedBack = mapper.Map<Order>(orderDto);
}
On mapping the Order
object
from OrderDto
, I get the following exception:
System.ArgumentException: System.Collections.Immutable.ImmutableHashSet`1[ReadOnlyCollectionDemo.OrderItem] needs to have a constructor with 0 args or only optional args. (Parameter 'type') at lambda_method(Closure , OrderDto , Order , ResolutionContext )
Any pointers to help fix this issue is highly appreciated.
Update
I somehow got it working through custom Converter
. But I really do not know how it worked. I updated my mapper configuration as follows:
var mapper = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new OrderProfile());
cfg.CreateMap(typeof(ICollection<>), typeof(ImmutableHashSet<>)).ConvertUsing(typeof(HashMapConverter<,>));
}).CreateMapper();
My HashMapConverter
class:
public class HashMapConverter<TCollection, TImmutableHashSet>
: ITypeConverter<ICollection<TCollection>, ImmutableHashSet< TImmutableHashSet>>
{
public ImmutableHashSet< TImmutableHashSet> Convert(
ICollection<TCollection> source,
ImmutableHashSet< TImmutableHashSet> destination,
ResolutionContext context)
{
return ImmutableHashSet<TImmutableHashSet>.Empty;
}
}
As you can see I'm just returning an empty ImmutableHashSet
, however, to my surprise the OrderDto
is now successfully mapped back to Order
with the correct number of OrderItems
Count
. I would have expected the Count
to be 0 since I'm returning an empty HashSet
. I suspect it is working because OrderItem
and OrderItemDto
are also mapped through AutoMapper
.
I would like to confirm my hypothesis and know if this is a correct approach.
As it turns out mapping an ImmutableHashSet
with the collection was not as straightforward as it seems. However, this made me realize that maybe I was not solving the correct problem. All I wanted was to ensure that my OrderItem
HashSet
cannot be modified outside the Order
class. I could easily achieve this by returning an IReadOnlyCollection
.
Here's my updated Order
class:
public class Order
{
private HashSet<OrderItem> _orderItems;
public Order()
{
_orderItems = new HashSet<OrderItem>();
}
public IReadOnlyCollection<OrderItem> OrderItems
{
get => _orderItems.ToImmutableHashSet();
private set => _orderItems = value.ToHashSet();
}
public void AddOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
public void RemoveOrderItem(OrderItem orderItem)
{
_orderItems.Add(orderItem);
}
}
IReadOnlyCollection
not only helps me to resolve the Automapper issue but in addition to this, it does not expose the Add
method. Hence, a user cannot accidentally call order.OrderItems.Add(orderItem)
method outside the Order
class.