Dreamer
Dreamer

Reputation: 636

Is there any way to get enum's string value using single character?

I'm using one enum in MVC application, which looks like the following enum,

public enum School
{
    DVB=1,
    AVD=2,
    ASB=3
}

And I'm getting the only single character from database for each school, e.g. getting:

D for DVB
A for AVD
B for ASB

I want to print the full name of enum value. I can achieve my goal by following way, i.e. I can edit enum and replace integer values of enum by character. But I don't think it's a good solution, because existing code is using integer values also.

Is there any other way to get enum's string value using single character?

Upvotes: 0

Views: 248

Answers (3)

CodeNotFound
CodeNotFound

Reputation: 23210

The cleanest way is to sit with your team to plan and accept to migrate your database and store an integer value.

Anyway based on your comments in your question, you're are using Entity Framework and map your entities to a view model. The following steps can help you without reflection, adding a new attribute or new enum.

Let say you're using an entity named MyEntity which contains actually contains a property named MySchool.

First instead of using School enum for the type MySchool property, you just use string type. So EF will just retrieve the data from the database and put it directly to the property. No conversion needed. So you'll have something like this:

public class MyEntity
{
    public string MySchool { get; set; }
}

Second, always into your MyEntity class add another property let name it MySchoolEnum which will be of type School enum and not mapped to the database so you have this:

[NotMapped] // <-- This tell EF that this property is not mapped to the database.
public School? MyShcoolEnum
{
    get
    {
        // Getting the value of this property depends on the value of the database
        // which is stored into MySchool property.
        switch (MySchool)
        {
            case "D": return School.DVB;
            case "A": return School.AVD;
            case "B": return School.ASB;
            default: return null;
        }
    }

    set
    {
        // When setting this property 
        // you automatically update the value of MySchool property
        switch (value)
        {
            case School.DVB: this.MySchool = "D";
                break;
            case School.AVD: this.MySchool = "A";
                break;
            case School.ASB: this.MySchool = "B";
                break;
            default: this.MySchool = null;
                break;
        }
    }
}

Side Note: With EF Core there is a more elegant way to do this kind of thing without adding a new property. With EF Core 2.1 we can rid of this property and use Value Conversion.

Finally, all view models that need to deal with MySchool column of the database should not use MySchool property but map their corresponding property to MyShcoolEnum property of your entity.

Your entity code should be similar to this:

public class MyEntity
{
    public string MySchool { get; set; }

    [NotMapped] // <-- This tell EF that this property is not mapped to the database.
    public School? MyShcoolEnum
    {
        get
        {
            // Getting the value of this property depends on the value of the database
            // which is stored into MySchool property.
            switch (MySchool)
            {
                case "D": return School.DVB;
                case "A": return School.AVD;
                case "B": return School.ASB;
                default: return null;
            }
        }

        set
        {
            // When setting this property you automatically update the value of MySchool property
            switch (value)
            {
                case School.DVB: this.MySchool = "D";
                    break;
                case School.AVD: this.MySchool = "A";
                    break;
                case School.ASB: this.MySchool = "B";
                    break;
                default: this.MySchool = null;
                    break;
            }
        }
    }
}

Upvotes: 2

JohnLBevan
JohnLBevan

Reputation: 24430

There are lots of ways to achieve this. A very simple option is to have a second enum with the alternative ids of your values; e.g.

void Main()
{
    Console.WriteLine((School)Enum.Parse(typeof(ConvertSchool),"A"));
}
public enum School
{
    DVB=1,
    AVD=2,
    ASB=3
}
public enum ConvertSchool
{
    D =1,
    A =2,
    B =3
}

Better is to create some kind of mapping; e.g. use a dictionary to map each char key to the related school value:

public enum School
{
    DVB=1,
    AVD=2,
    ASB=3
}
readonly IDictionary SchoolMap = new Dictionary<char,School>() {
    {'D', School.DVB},
    {'A', School.AVD},
    {'B', School.ASB}
};
void Main()
{
    Console.WriteLine(SchoolMap['A']);
}

That said, these are prone to error (e.g. if someone created a new school without making a new ConvertSchool entry / dictionary mapping). A better way is to switch from an enum to a class, e.g.

void Main()
{
    Console.WriteLine((School)'A');
}
public class School
{
    //list of enums
    public static readonly List<School> Values = new List<School>();
    //"enum" values
    public static readonly School DVB = new School("DVB",'D',1);
    public static readonly School AVD = new School("AVD",'A',2);
    public static readonly School ASB = new School("ASB",'B',3);
    //properties
    public string Name {get;private set;}
    public char Code {get;private set;}
    public int Index {get;private set;}
    //constructor
    private School (string name, char code, int index)
    {
        if (Values.Exists(x => x.Name.Equals(name)) || Values.Exists(x => x.Code.Equals(code)) || Values.Exists(x => x.Index.Equals(index)))
        {
            throw new ArgumentException(string.Format("The Name, Code, and Index of each School value must be unique.  \nName: '{0}'\nSchool: '{1}'\nIndex: {2}", name, code, index));
        }
        Name = name;
        Code = code;
        Index = index;
        Values.Add(this);
    }
    //implicit conversion
    public static implicit operator School(string schoolName)
    {
        return Values.First(x => x.Name.Equals(schoolName));
    }
    public static implicit operator School(char schoolCode)
    {
        return Values.First(x => x.Code.Equals(schoolCode));
    }
    public static implicit operator School(int index)
    {
        return Values.First(x => x.Index.Equals(index));
    }
    //how should it be displayed?
    public override string ToString()
    {
        return Name;
    }
    //whatever other logic you need
}

With this last example, if a character is passed for which there is no corresponding School (e.g. Console.WriteLine((School)'X');) you'd get an InvalidOperationException. If you don't want errors here, but would rather receive null values, replace the First with FirstOrDefault in each of the implicit conversion statements.


An alternate solution is to hold the mapping in your database layer; either creating a table with the mapped values, or even holding these mappings in the definition of a view; then using a view over the table joined to the mapping to retrieve the 3 char code instead of the 1 char code for each school; e.g.

--if you have a SchoolMap table relating the 1 char code to the 3 char code:

create table SchoolMap 
(
    Char1Code nchar(1) not null primary key clustered
    , Char3Code nvarchar(3) not null unique
)

create view SchoolView as 
select School.*
, SchoolMap.Char3Code
from School
left outer join SchoolMap 
on SchoolMap.Char1Code = School.Code 


--if you don't have and don't want a table holding the mapping, you can do the same in the view's definition:

create view SchoolView as 
select School.*
, SchoolMap.Char3Code
from School
left outer join 
(
    values ('D','DVB'),('A','AVD'),('B','ASB')
) SchoolMap (Char1Code, Char3Code)
on SchoolMap.Char1Code = School.Code 

Upvotes: 1

xanatos
xanatos

Reputation: 111870

A very simple class for "decorating" attribute values with a [MapTo("x")] attribute:

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class MapToAttribute : Attribute
{
    public readonly string Key;

    public MapToAttribute(string key)
    {
        Key = key;
    }
}

public static class MapToUtilities<T> where T : struct
{
    private static readonly Dictionary<string, T> keyValues = Init();

    private static Dictionary<string, T> Init()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(nameof(T));
        }

        var values = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static);

        var keyValues2 = new Dictionary<string, T>();

        foreach (FieldInfo fi in values)
        {
            var attr = fi.GetCustomAttribute<MapToAttribute>();
            if (attr == null)
            {
                continue;
            }

            string key = attr.Key;

            T value = (T)fi.GetValue(null);

            keyValues2.Add(key, value);
        }

        return keyValues2;
    }

    public static T KeyToValue(string key)
    {
        return keyValues[key];
    }

    public static string ValueToKey(T value)
    {
        var cmp = EqualityComparer<T>.Default;
        return keyValues.First(x => cmp.Equals(x.Value, value)).Key;
    }
}

Usage:

public enum School
{
    [MapTo("D")]
    DVB = 1,
    [MapTo("A")]
    AVD = 2,
    [MapTo("B")]
    ASB = 3
}

and then:

School sc = MapToUtilities<School>.KeyToValue("A");
string key = MapToUtilities<School>.ValueToKey(sc);

Upvotes: 1

Related Questions