Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF 4.1 Code First - map enum wrapper as complex type

I'm trying to build a generic solution to the problem of enums with EF 4.1. My solution is basically a generic version of How to fake enums in ef 4. The enum wrapper class works wonderfully in the rest of the code and allows code like:

EnumWrapper<Color> c = Color.Red;

Here's the enum wrapper class:

public class EnumWrapper<TEnum> where TEnum : struct, IConvertible
{
    public EnumWrapper()
    {
        if (!typeof(TEnum).IsEnum)
            throw new ArgumentException("Not an enum");
    }

    public TEnum Enum { get; set; }

    public int Value
    {
        get { return Convert.ToInt32(Enum); }
        set { Enum = (TEnum)(object)value; }
    }

    public static implicit operator TEnum(EnumWrapper<TEnum> w)
    {
        if (w == null) return default(TEnum);
        else return w.Enum;
    }

    public static implicit operator EnumWrapper<TEnum>(TEnum e)
    {
        return new EnumWrapper<TEnum>() { Enum = e };
    }

    public static implicit operator int(EnumWrapper<TEnum> w)
    {
        if (w == null)
            return Convert.ToInt32(default(TEnum));
        else
            return w.Value;
    }
}

enum:

public enum Color { red = 1, green = 2, blue = 3 }

POCO:

public class ChickenSandwich 
{
    public ChickenSandwich() {
        CheeseColor = new EnumWrapper<Color>();
    }

    public int ID { get; set; }
    public string Name { get; set; }
    public EnumWrapper<Color> CheeseColor { get; set; }
}

Mapping:

public class ColorMapping : ComplexTypeConfiguration<EnumWrapper<Color>> 
{
    public ColorMapping() {
        Ignore(x => x.Enum);
        Property(x => x.Value);
    }
}

I've also tried mapping it on the ChickenSandwich's EntityTypeConfiguration like this:

Property(x => x.CheeseColor.Value).HasColumnName("CheeseColor");

If I leave it up to the ColorMapping and do no explicit mapping on the ChickenSandwichMapping, it just doesn't put it in the database. If I map it the x.CheeseColor.Value way, I get the dreaded:

System.InvalidOperationException: The configured property 'CheeseColor' is not a declared property on the entity 'ChickenSandwich'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property..


Edit

I wasn't able to get the generic version of the enum wrapper working, so I've gone with writing individual wrappers. It's not exactly what I wanted because it violates the DRY principle, but it does allow me to query the column as an enum.

[ComplexType]
public class ColorWrapper
{
    [NotMapped]
    public Color Enum { get; set; }

    public int Value
    {
        get { return (int)Enum; }
        set { Enum = (Color)value; }
    }

    public static implicit operator Color(ColorWrapper w)
    {
        if (w == null) return default(Color);

        return w.Enum;
    }

    public static implicit operator ColorWrapper(Color c)
    {
        return new ColorWrapper { Enum = c };
    }
}

I had to use the ColorWrapper on the ChickenSandwich class. It works more or less transparently. Then had to add this to my mapping class constructor to get the column name I wanted:

Property(x => x.CheeseColor.Value).HasColumnName("CheeseColorId");
like image 590
Nathan Craddock Avatar asked Apr 14 '11 17:04

Nathan Craddock


3 Answers

There's a much simpler way to map enums in EF 4: just create an int property on your ChickenSandwich class to represent the int value of the enum. That's the property that EF should map, then have a "mini wrapper" property to allow you to use the enum

public class ChickenSandwich 
{   
    public int ID { get; set; }
    public string Name { get; set; }

    // This property will be mapped
    public int CheeseColorValue { get; set; }

    public Color CheseColor
    {
        get { return (Color) CheeseColorValue; }
        set { CheeseColorValue = (int) value; }
    }
}

I actually don't have to use the Fluent API or any kind of attribute decoration for this to work. On generating the database, EF will happily ignore any type it doesn't know how to map, but the int property will be mapped.

I have tried mapping enums based on that article too, but it caused me no end of headaches. This method works well, and you could adapt your solution to use your wrapper as the "mapping" property, i.e. CheeseColor in this case.

like image 79
Sergi Papaseit Avatar answered Nov 19 '22 04:11

Sergi Papaseit


I got Nathan generic enum wrapper class to work by simply making it abstract and move:

public static implicit operator EnumWrapper <TEnum> (TEnum e)

to the derived classes like this:

public class CategorySortWrapper : EnumWrapper<CategorySort>
{
    public static implicit operator CategorySortWrapper(CategorySort e)
    {
        return new CategorySortWrapper() { Enum = e };
    }
}

public abstract class EnumWrapper<TEnum> where TEnum : struct, IConvertible
{
    public EnumWrapper()
    {
        if (!typeof(TEnum).IsEnum)
            throw new ArgumentException("Not an enum");
    }

    public TEnum Enum { get; set; }

    public int Value
    {
        get { return Convert.ToInt32(Enum); }
        set { Enum = (TEnum)(object)value; }
    }

    public static implicit operator int(EnumWrapper<TEnum> w)
    {
        if (w == null)
            return Convert.ToInt32(default(TEnum));
        else
            return w.Value;
    }
}

in my code I just using the it like this

public CategorySortWrapper ChildSortType { get; set; }

category.ChildSortType = CategorySort.AlphabeticOrder;

I didn't do anything else and EF 4.1 created a "ComplexType like field" in the database named ChildSortType_Value

like image 41
Henrik Stenbæk Avatar answered Nov 19 '22 04:11

Henrik Stenbæk


That might be the best option for enums, but another idea would be to just use constants instead of enums:

static void Main(string[] args)
{
    Console.WriteLine("There are {0} red chicken sandwiches.", 
        sandwiches.ChickenSandwiches
                  .Where(s => s.Color == Color.red)
                  .ToArray().Length);
}

public struct Color
{
    public const int red = 1;
    public const int green = 2;
}

public class ChickenSandwich
{
    public ChickenSandwich()
    {
    }

    public int ID { get; set; }
    public string Name { get; set; }
    public int Color { get; set; }
}

public class Sandwiches : DbContext
{
    public DbSet<ChickenSandwich> ChickenSandwiches { get; set; }
}
like image 27
Alan Jackson Avatar answered Nov 19 '22 05:11

Alan Jackson