简体   繁体   中英

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?

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.

You can achieve an expression based enum description mapping by building up an expression that evaluates to a string containing a condition statement (such as switch/if/case depending on how the provider implements it) with the enum descriptions as the results.

Because the enum descriptions can be extracted ahead of time we can obtain them and use them as a constant for the result of the condition expression.

Note: I've used the above extension method GetDescription() but you can use whatever flavour of attribute extraction you need.

  public static Expression<Func<TEntity, string>> CreateEnumDescriptionExpression<TEntity, TEnum>(
    Expression<Func<TEntity, TEnum>> propertyExpression)
     where TEntity : class
     where TEnum : struct
  {
     // Get all of the possible enum values for the given enum type
     var enumValues = Enum.GetValues(typeof(TEnum)).Cast<Enum>();

     // Build up a condition expression based on each enum value
     Expression resultExpression = Expression.Constant(string.Empty);
     foreach (var enumValue in enumValues)
     {
        resultExpression = Expression.Condition(
           Expression.Equal(propertyExpression.Body, Expression.Constant(enumValue)),
           // GetDescription() can be replaced with whatever extension 
           // to get you the needed enum attribute.
           Expression.Constant(enumValue.GetDescription()),
           resultExpression);
     }

     return Expression.Lambda<Func<TEntity, string>>(
        resultExpression, propertyExpression.Parameters);
  }

Then your Automapper mapping becomes:

  cfg.CreateMap<Order, OrderDTO>().ForMember(
     dest => dest.Status, opts => opts.MapFrom(
             CreateEnumDescriptionExpression<Order, OrderStatus>(src => src.Status)));

When this is evaluated at runtime using Entity Framework with SQL server provider, the resulting SQL will be something like:

SELECT 
   -- various fields such as Id
   CASE WHEN (2 = [Extent1].[Status]) THEN N'Delivered' 
        WHEN (1 = [Extent1].[Status]) THEN N'Processing' 
        WHEN (0 = [Extent1].[Status]) THEN N'Paid' ELSE N'' END AS [C1]
FROM [Orders] as [Extent1]

This should also work for other Entity Framework DB providers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM