Reputation: 636
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
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
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
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