0

I seem to often (when dealing with enums) do things like this:

.Select(x => new {
    enumDesc = (
        x.status == GoodStatus ? "Good!"
        : x.status == BadStatus ? "Bad<angryface>"
        : x.status == Unknown ? "no clue"
    )
})

or

let enumDesc = (
    x.status == GoodStatus ? "Good!"
    : x.status == BadStatus ? "Bad<angryface>"
    : x.status == Unknown ? "no clue"
)

Some queries end up having dozens of these conditions, like a project I'm working on now. Doing it this way is nice because it can be translated into a SQL case statement so it's faster than iterating through after materialization. In my current project, however, I need to do this in more than one place/query. Is there any way to do this in a performant and reusable way (i.e., translates to SQL) without refactoring the structures (e.g. moving the enum into a table)? I haven't been able to come up with one. If I could capture or alias just the ternary stuff, that would be good enough, or making an expression that "selects" from constants or attributes or something...

Josh
  • 6,944
  • 8
  • 41
  • 64
  • iirc you can create something like `Func Convert` and then use like `enumDesc = Convert(x)` – AD.Net Apr 28 '16 at 14:36
  • You could have a look at the _CustomAttribute_ for your enum members. And then use it in combination with an ExtensionMethod. Something like this http://stackoverflow.com/questions/1799370/getting-attributes-of-enums-value – schlonzo Apr 28 '16 at 14:38
  • I would not convert the value on the model, retrieve the value and leave it as-is, then when you represent it do the conversion using a general function. – Gusman Apr 28 '16 at 14:39

2 Answers2

0

If you're open to avoiding if logic, consider the following:

There are often times where I see developers in C# lean towards using the DescriptionAttribute and combining that with an extension method to lookup the enum values' description value.

public enum Status
{
    [Description("no clue")]
    Unknown,
    [Description("Good!")]
    Good,
    [Description("Bad<angryface>")]
    Bad
}

public static class EnumExt
{
    public static string Description<T>(this T source)where T : struct, IConvertible
    {
        if (!typeof (T).IsEnum)
        {
            throw new ArgumentException("T must be an enumerated type");
        }

        var fi = source.GetType().GetField(source.ToString());
        var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof (DescriptionAttribute), false);
        return attributes.Length > 0 ? attributes[0].Description : source.ToString();
    }
}

Here's a working .NET Fiddle I threw together quick to demonstrate this.

Otherwise, if you want to map a string to an enum value without the usage of attributes -- I'd suggest a switch instead...

David Pine
  • 23,787
  • 10
  • 79
  • 107
  • I haven't tested it yet but from my understanding this wouldn't work before materializing the entities into objects, meaning it won't happen at the SQL layer, so I would need to iterate through the objects an additional time. – Josh Apr 28 '16 at 15:22
  • No, wherever you need to use the value of the enum -- instead use the `.Description()`. Or if it's easier create a read-only property on the entity that evaluates as `public string StatusDesc { get { return Status.Description(); } }`. Then you can use the `.StatusDesc` for the value you want. – David Pine Apr 28 '16 at 15:33
  • With this I get: Exception thrown: 'System.NotSupportedException' in EntityFramework.dll Additional information: LINQ to Entities does not recognize the method 'System.String Description[Status](Status)' method, and this method cannot be translated into a store expression. – Josh Apr 28 '16 at 16:57
0

You can use a method that implements an Expression<Func<T, T2>>, see example below:

    public enum Status
    {
        GoodStatus,
        BadStatus,
        Unknown
    }

    public class foo
    {
        public Status status;
    }

    public void Execute()
    {
        List<foo> list = new List<foo>
        {
            new foo
            {
                status = Status.BadStatus
            },
            new foo
            {
                status = Status.GoodStatus
            },
            new foo
            {
                status = Status.GoodStatus
            },
            new foo
            {
                status = Status.Unknown
            }
        };


        var descStatus = list.Select(GetStatus).ToList();
    }

    public Expression<Func<foo, object>> GetStatus(foo _foo)
    {
        return x => new
        {
            enumDesc = _foo.status == Status.GoodStatus
                ? "Good!"
                : _foo.status == Status.BadStatus
                    ? "Bad<angryface>"
                    : _foo.status == Status.Unknown ? "no clue" : ""
        };
    }
Julio Borges
  • 661
  • 14
  • 29
  • Hmm I tried this based on another page I found, but it didn't work. I'll try again based on your example. – Josh Apr 28 '16 at 15:23
  • With this I get: Exception thrown: 'System.NotSupportedException' in EntityFramework.dll Additional information: Unable to create a constant value of type 'OrderItemStatusClass'. Only primitive types or enumeration types are supported in this context. – Josh Apr 28 '16 at 17:02
  • This does not run code with IQueryable coming from a context. if you need running on a Queryable, include .ToList() before the .Select(...) – Julio Borges Apr 28 '16 at 17:48