3

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

BBauer42
  • 3,549
  • 10
  • 44
  • 81

1 Answers1

1

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)))
Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
  • this looks fantastic, but unfortunately my less than stellar expression skills are leaving me a bit high and dry. I'm not understanding the parameters you are passing to the method, specifically `(Entry e) => e.ReviewStatus))` is `Entry` an enum type? – crichavin May 15 '19 at 23:10
  • @ChadRichardson Entry in this case is the Entity, and ReviewStatus is a property on Entry which is an enum type. So the whole expression has the effect of causing the ReviewStatus property on Entry being converted to a string and stored in the ReviewStatus property on EntryListItem. – Samuel Jack May 21 '19 at 10:56
  • For anyone else dense like me, since your source (database field) is not an enum type, you need to cast it to one while passing it to this expression method. `.ForMember(e => e.ShippingContainerHeaderStatusDesc, c => c.MapFrom(CommonBusinessObjects.ViewModelMaps.AutoMapperExtensions.CreateEnumToStringExpression((ShippingContainerDetail e) => (ShippingContainerHeader.StatusOptions)e.ShippingContainerHeader.StatusId)))`. Note the case of the database column (`ShippingContainerStatus.StatusId` an `int`) to the enum of type `ShippingContainerHeader.StatusOptions` – crichavin Oct 12 '20 at 22:48