views:

390

answers:

2

I asked a previous question about mapping an enumerated value on a table using LinqToSql and the answer was to use the O/R Designer to set the type to global::MyNamespace.TMyEnum.

That works OK if your enumeration is based on an integer. But what if your enum is based on a string value? The most obvious application of this is:

public enum TGender {
  Male,
  Female,
  Unknown,
}

where the equivalent values on the database are M, F and U. You can't type an enumeration as a string, so how can you make the Gender property of your LinqToSql object return a TGender value?

+3  A: 

I'd add a getter property to my LINQ to SQL object that looks something like this:

public TGender Gender
{
   switch(this.DbGender)
   {
      case "M":
        return TGender.Male;
   }
}

Or, if you don't want to use a huge switch statement, you can add an attribute to your enum, that has a string property that represents the string value in the database for that value. Then, use reflection, get the correct enum, and return that.

Here's a sample of doing it using Reflection and Attributes:

public class EnumAttribute : Attribute
{
    public string DbValue
    {
        get;
        set;
    }
}

public enum TGender
{
    [EnumAttribute(DbValue = "M")]
    Male,
    [EnumAttribute(DbValue = "F")]
    Female,
    [EnumAttribute(DbValue = "U")]
    Unknown
}

    public TGender GetEnumValue(string s)
    {
        foreach (TGender item in Enum.GetValues(typeof(TGender)))
        {
            FieldInfo info = typeof(TGender).GetField(item.ToString());
            var attribs = info.GetCustomAttributes(typeof(EnumAttribute), false);
            if (attribs.Length > 0)
            {
                EnumAttribute a = attribs[0] as EnumAttribute;
                if (s == a.DbValue)
                {
                    return item;
                }
            }
        }

        throw new ArgumentException("Invalid string value.");
    }

To test it out:

var item = GetEnumValue("M");
Console.WriteLine(item);

This results in "Male".

BFree
OK... but how do you get Linq automatically to cast "M" to TGender.Male, "F" to TGender.Female, etc? I don't want to have separate properties for "DbGender" (string) and "Gender" (TGender) - the string property has no use to me at all, and I don't want to pollute my code with unnecessary interfaces.
Shaul
In this case there is no way. The best you can do is make the DbGender property internal, and the Gender public. Yes, it's not ideal, but I don't think there's another way.
BFree
OK, then I guess the "switch" statement is the way to go. Thanks...
Shaul
A: 

Allow me to suggest going the route of behavior classes instead of enums. You'll eliminate switch statements completely and have the ability to associate meta data and behaviors on an item by item basis.

public class Gender
{
    private static readonly Dictionary<string, Gender> _items = new Dictionary<string, Gender>();

    public static readonly Gender Male = new Gender("M", "he", age => age >= 14);
    public static readonly Gender Female = new Gender("F", "she", age => age >= 13);
    public static readonly Gender Unknown = new Gender("U", "he/she", age => null);

    public string DatabaseKey { get; private set; }
    public string Pronoun { get; private set; }
    public Func<int, bool?> CanGetMarriedInTexas { get; set; }

    private Gender(string databaseKey, string pronoun, Func<int,bool?> canGetMarriedInTexas)
    {
     DatabaseKey = databaseKey;
     Pronoun = pronoun;
     CanGetMarriedInTexas = canGetMarriedInTexas;
     _items.Add(databaseKey, this);
    }

    public static Gender GetForDatabaseKey(string databaseKey)
    {
     if (databaseKey == null)
     {
      return Unknown;
     }
     Gender gender;
     if (!_items.TryGetValue(databaseKey, out gender))
     {
      return Unknown;
     }
     return gender;
    }

    public IEnumerable<Gender> All()
    {
     return _items.Values;
    }
}

Instead of using a switch to get the database key you just ask for it:

public void MapViewToPerson(IEditPersonInfoView view, Person person)
{
    person.Gender = view.Gender.DatabaseKey;
    // ...
}

You can also apply gender specific behaviors by simply asking the Gender object for the information you want. For example, the legal marriage-with-consent age in Texas is gender specific.

public void MapPersonToView(IEditPersonInfoView view, Person person)
{
    Gender gender = Gender.GetForDatabaseKey(person.Gender);
    view.Gender = gender;
    view.ShowMarriageSection = gender.CanGetMarriedInTexas(person.AgeInYears) ?? true;
    // ...
}
Handcraftsman