15
\$\begingroup\$

I'm using Entity Framework Code First CTP 5 for a current project. There is still no enum support in Entity Framework so this is what I am using in order to avoid having magic numbers or casts littered throughout my code.

Simplified Example:

Domain Model Consumed by Code First

public class Address
{
    // This would normally be an enum
    public int Type { get; set; }
}

Repository

public class AddressRepository
{
    public Add(Address address)
    {
        // Compare using enum semantics
        if (address.Type == AddressType.Home)
            // Do something

        context.Add(address);
    }
}

View Model

public class AddressVM
{
    // Using the AddressType class for the view model
    public AddressType Type { get; set; }     

    public AddressVM(Address address)
    {
        this.Type = address.Type;
    }
}

Enum Replacement Class

public interface IEnumClass
{
    List<string> Properties { get; }
}

public class AddressType : IEnumClass
{
    public static int Home { get { return 0; } }
    public static int Work { get { return 1; } }

    public List<string> Properties { get { return properties; } }

    private List<string> properties;
    private int id;

    public AddressType()
    {
        SetProperties();
    }
    public AddressType(int id)
    {
        this.id = id;
        SetProperties();
    }

    private void SetProperties()
    {
        properties = this.GetType().GetProperties(BindingFlags.Public|BindingFlags.Static)
            .Select(x => x.Name).ToList();
    }

    public static implicit operator AddressType(int id)
    {
        return new AddressType(id);
    }

    public static implicit operator String(AddressType addressType)
    {
        return addressType.properties[addressType.id];
    }

    public static implicit operator int(AddressType addressType)
    {
        return addressType.id;
    }
}

My Personal Assessment

Pros:

  • Allows me to write business rules without using magic numbers
  • Uses the same semantics/syntax as enums for easy refactoring down the road
  • Allows for an implicit conversion to a string for the view model
  • My select list factory class can easily be made to accept any IEnumClass and generate a select list for the view model

Cons:

  • A little verbose
  • As far as I can tell it is impossible to use a nice abstract generic class because all the actual work uses implicit type casting

I'm asking for feedback here because I'm a total novice programmer so I want to solicit some critiques before implementing something I just came up with!

\$\endgroup\$
1
  • \$\begingroup\$ I second Saeed's approach. Implementations here also might be of interest: C# String Enums \$\endgroup\$
    – HAL9000
    May 11 '11 at 17:36
5
\$\begingroup\$

I think the biggest drawback to your approach is that you're adding complexity but not really getting the main benefit you're after, which is to avoid magic numbers. This code doesn't prevent you from using magic numbers since it automatically converts from any int to an AddressType, and it still exposes its values as an int. So I would suggest one of the following two approaches. The first one is very simple but seems to give the same benefits as your code. The second one is more along the lines of what you're after, so there's some complexity, but has some added benefits as well.

Simple solution

I think the simplest thing would be to just have a static class with constants defined in it, like so:

public static class AddressType
{
    public const int Home = 1;
    public const int Work = 2;
}

(Note: as with any enum, 0 should not be a valid value)

You would then store your values as int on your entities, but always compare them against these constants. This would give you the same usage pattern as you showed in your question.

More complicated solution

Alternatively, if you want to go with your approach, you should restrict it to only allow you to instantiate an AddressType with known values, which will prevent the use of magic numbers and make it more strongly typed than using a plain int.

So for example, something like the following:

public class AddressType
{
    public static readonly AddressType Home = new AddressType(1);
    public static readonly AddressType Work = new AddressType(2);

    private int value;

    private AddressType(int value)
    {
        this.value = value;
    }

    public override bool Equals(object obj)
    {
        AddressType rhs = obj as AddressType;
        return
            rhs != null &&
            this.value == rhs.value;
    }

    public override int GetHashCode()
    {
        return this.value.GetHashCode();
    }
}

In your model, you would use it like so:

public class Address
{
    public AddressType AddressType { get; set; }
}

Now assigning and comparing these AddressType values looks just like an enum, but explicitly prevents you from using any magic numbers. If you really need it, you can also add conversions to/from int, but only allow it if they are known values.

You could use something like the following, though if you have more than 2 values you'll probably want to use a dictionary to map from int to AddressType:

public static implicit operator AddressType(int value)
{
    if (value == AddressType.Home.value)
    {
        return AddressType.Home;
    }
    else if (value == AddressType.Work.value)
    {
        return AddressType.Work;
    }

    throw new InvalidCastException();
}
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the suggestions! I'll definitely put some logic in there to make sure any int passed in are valid. \$\endgroup\$ Mar 6 '11 at 19:59
10
\$\begingroup\$

I keep seeing all these really complex solutions to the lack of enum support, but most make things tricky when taking into account that next entity framework release is probably going to have enum support.

Here's my solution. A sample class, before our workaround:

public class Widget
{
    public int ID { get; set; }
    public Status Status { get; set; }
}

Obviously Status won't be saved as it's an enum, and if we provide a fancy workaround, things will probably break when we upgrade to the next entity framework release, as it'll suddenly start trying to read and write that property. So we do the following:

public class Widget
{
    public int ID { get; set; }
    [NotMapped] // stop future entity frameworks from accidentally using this column
    public Status Status { get; set; }

    [Column("Status")]
    private int StatusEnum
    {
        get { return (int)Status; }
        set { Status = (Status)value; }
    }
}

The new property is private so we can't see it externally, so it doesn't exist to the outside world for all intents and purposes. The entity framework can see it as you've marked it with a [Column] attribute and so it'll be found via reflection. The original enum column will be skipped even if we upgrade to a later version of the framework, and then all we have to do is remove those few lines of workaround code and things should still work seamlessly.

The result?

  • The class looks the same to anything consuming it, but now enums are saved and loaded
  • No additional code complexity
  • Future-proofed against entity framework upgrades
  • Super easy to remove the additional code when upgrading to a version of the framework that supports enum persistence
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.