Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 987
Sergio Avatar asked Jun 05 '18 00:06

Sergio


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


2 Answers

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.

like image 112
John Wu Avatar answered Oct 20 '22 07:10

John Wu


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.

like image 21
mips Avatar answered Oct 20 '22 05:10

mips