Search code examples
c#reflectionenumsautomapperprojection

c# - Enum description to string with AutoMapper


I'm trying to project from my Order model to my OrderDTO model. Order has an enum. The problem is that projection doesn't work if I try to to get the Description attribute from the Enum. Here it's my code:

  • OrderStatus.cs:

    public enum OrderStatus {
        [Description("Paid")]
        Paid,
    
        [Description("Processing")]
        InProcess,
    
        [Description("Delivered")]
        Sent
    }
    
  • Order.cs:

    public class Order {
        public int Id { get; set; }
        public List<OrderLine> OrderLines { get; set; }
        public OrderStatus Status { get; set; }
    }
    
  • OrderDTO.cs:

    public class OrderDTO {
        public int Id { get; set; }
        public List<OrderLineDTO> OrderLines { get; set; }
        public string Status { get; set; }  
    }
    

With this following configuration in my AutoMapper.cs:

cfg.CreateMap<Order, OrderDTO>().ForMember(
    dest => dest.Status,
    opt => opt.MapFrom(src => src.Status.ToString())
);

Projection works, but I get an OrderDTO object like this:

 - Id: 1
 - OrderLines: List<OrderLines>
 - Sent //I want "Delivered"!

I don't want Status property to be "Sent", I want it to be as its associated Description attribute, in this case, "Delivered".

I have tried two solutions and none of them have worked:

  1. Using ResolveUsing AutoMapper function as explained here, but, as it's stated here:

ResolveUsing is not supported for projections, see the wiki on LINQ projections for supported operations.

  1. Using a static method to return the Description attribute in String by Reflection.

    cfg.CreateMap<Order, OrderDTO>().ForMember(
        dest => dest.Status,
        opt => opt.MapFrom(src => EnumHelper<OrderStatus>.GetEnumDescription(src.Status.ToString()))
    );
    

But this gives me the following error:

LINQ to Entities does not recognize the method 'System.String GetEnumDescription(System.String)' method, and this method cannot be translated into a store expression.

Then, how can I achieve this?


Solution

  • You can add an extension method like this one (borrowed the logic from this post):

    public static class ExtensionMethods
    {
        static public string GetDescription(this OrderStatus This)
        {
            var type = typeof(OrderStatus);
            var memInfo = type.GetMember(This.ToString());
            var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
            return ((DescriptionAttribute)attributes[0]).Description;
        }
    }
    

    Then access it in your map:

    cfg => 
    {
        cfg.CreateMap<Order, OrderDTO>()
        .ForMember
        (
            dest => dest.Status,
            opt => opt.MapFrom
            (
                src => src.Status.GetDescription()
            )
        );
    }
    

    This results in what you are asking for:

    Console.WriteLine(dto.Status);  //"Delivered", not "sent"
    

    See a working example on DotNetFiddle

    Edit1: Don’t think you can add a local look up function like that to LINQ to entities. It would only work in LINQ to objects. The solution you should pursue perhaps is a domain table in the database that allows you to join to it and return the column that you want so that you don’t have to do anything with AutoMapper.