enumsentity-framework-6automapperiqueryableautomapper-4

How to map an int to its enum description using AutoMapper during a queryable projection?


Here is the enum extension method to get its description attribute.

public static string GetDescription(this Enum enumeration)
{
    if (enumeration == null)
        throw new ArgumentNullException();

    var value = enumeration.ToString();
    var type = enumeration.GetType();
    var descriptionAttribute =
        (DescriptionAttribute[]) type.GetField(value).GetCustomAttributes(typeof (DescriptionAttribute), false);

    return descriptionAttribute.Length > 0 ? descriptionAttribute[0].Description : value;
}

Here is the source object:

public class Account {
    public int AccountId {get;set;}
    public int AccountStatusId {get;set;}
}

Here is the enum:

public enum AccountStatus {
    [Description("N/A")]
    None,
    [Description("OPEN")]
    Open,
    [Description("CLOSED")]
    Closed,
    [Description("BAD CREDIT")
    Problem
}

Here is the destination object:

public class GetAccountResponse {
    public int AccountId {get;set;}
    public string Status {get;set;}
}

Here is my attempt to map (using the latest non-static automapper version). Remember this is during an EF queryable projection.

_config = new MapperConfiguration(cfg => cfg.CreateMap<Account, GetAccountsResponse>()
    .ForMember(dest => dest.Status,
        opts => opts.MapFrom(src => ((AccountStatus) src.AccountStatusId).GetDescription())));

Here is the projection where query is an IQueryable<Account>:

query.ProjectToList<GetAccountResponse>(_config);

This is the exception I get:

Can't resolve this to Queryable Expression


Solution

  • If you check out the signature of the MapFrom method, you'll notice that one of the overloads takes a parameter of type Expression<Func<TSource, TMember>>.

    This suggests that you could write a method which builds an expression tree from ternary expressions that can convert any possible value of your enum to its appropriate string. AutoMapper would then convert this into the appropriate SQL expression via LINQ.

    Here's an example which just uses the Enum names themselves: you should be able to adapt it straightforwardly to use your Descriptions:

    public static class EnumerableExpressionHelper
    {
        public static Expression<Func<TSource, String>> CreateEnumToStringExpression<TSource, TMember>(
            Expression<Func<TSource, TMember>> memberAccess, string defaultValue = "")
        {
            var type = typeof(TMember);
            if (!type.IsEnum)
            {
                throw new InvalidOperationException("TMember must be an Enum type");
            }
    
            var enumNames = Enum.GetNames(type);
            var enumValues = (TMember[])Enum.GetValues(type);
    
            var inner = (Expression)Expression.Constant(defaultValue);
    
            var parameter = memberAccess.Parameters[0];
    
            for (int i = 0; i < enumValues.Length; i++)
            {
                inner = Expression.Condition(
                Expression.Equal(memberAccess.Body, Expression.Constant(enumValues[i])),
                Expression.Constant(enumNames[i]),
                inner);
            }
    
            var expression = Expression.Lambda<Func<TSource,String>>(inner, parameter);
    
            return expression;
        }
    }
    

    You would use it as follows:

    CreateMap<Entry, EntryListItem>()
                .ForMember(e => e.ReviewStatus,
                    c => c.MapFrom(EnumerableExpressionHelper.CreateEnumToStringExpression((Entry e) => e.ReviewStatus)))