Entity Framework 4.1 Code First – Mapping Enums to Lookup Tables

This afternoon I Built a mechanism for mapping Enums to Lookup Tables in EF Code First - using complex types and implicit operators. The steps are as follows:

Step 1 – the Enum

// Avoid 0 values – SQL INT default value

    public enum JobInstanceStateEnum : int

    {

        Ok = 1,

        MissingResources = 2,

        DataFailure = 3,

        Aborted = 4

    }

Step 2 – the POCO for generating the Lookup Table

// As we are using id as FK, and it must match the enum, overide convention of identity keygen

    public class JobInstanceStateLookup

    {

        [DatabaseGenerated(DatabaseGenerationOption.None)]

        public int JobInstanceStateLookupId { get; set; }

        public string Value { get; set; }

    }

Step 3 – the POCO for generating the Entity Table

// Entity

    public class JobInstance

    {

        public int JobInstanceId { get; set; }  // convention PK      

        public JobInstanceState Status { get; set; } // enum ComplexType mapper

        public string blah { get; set; }

    }

Step 4 – the ComplexType POCO for mapping the enum

 // Map

    [ComplexType]

    public class JobInstanceState

    {

        // FK + navigation property (must be virtual)

        [ForeignKey("JobInstanceStateLookup")]

        public int JobInstanceStateLookupId { get; set; }

        public virtual JobInstanceStateLookup JobInstanceStateLookup { get; set; }

        // ctors

        public JobInstanceState() : this(JobInstanceStateEnum.Ok) {}

        public JobInstanceState(JobInstanceStateEnum value)

        {

            JobInstanceStateLookupId = (int)value;

        }

        // implicit operators for auto casting the complex type to enum val

        public static implicit operator JobInstanceStateEnum(JobInstanceState type)

        {

            return (JobInstanceStateEnum)type.JobInstanceStateLookupId;

        }

        public static implicit operator JobInstanceState(JobInstanceStateEnum type)

        {

            return new JobInstanceState(type);

        } 

    }

Step 5 – DBContext and DBSets

 // DbContext – the Generator

    public class SpitfireContext : DbContext

    {   

        public DbSet<JobInstanceStateLookup> JobInstanceStateLookup { get; set; }

        public DbSet<JobInstance> JobInstances { get; set; }

        …

    }

Step 6 – DB Initializer

// Initialize the DB – iterate over enum to populate lookup table

        public class Initializer : IDatabaseInitializer<MyContext>

        {

            public void InitializeDatabase(MyContext context)

            {

                if (!context.Database.Exists() || !context.Database.CompatibleWithModel(false))

                {

                    context.Database.Delete();

                    context.Database.Create();

                    var jobInstanceStateList = EnumExtensions.ConvertEnumToDictionary<JobInstanceStateEnum>().ToList();

                    jobInstanceStateList.ForEach(kvp => context.JobInstanceStateLookup.Add(

                        new JobInstanceStateLookup()

                            {

                                JobInstanceStateLookupId = kvp.Value,

                                Value = kvp.Key

                            }));

                    context.SaveChanges();

                }          

            }

        }

Step 7  - Unit Test

        [TestMethod]

        public void TestComplexTypeWithEnumToLookup()

        {

            // Arrange:

            DbDatabase.SetInitializer(new MyContext.Initializer());

            var db = new MyContext();

            var ji = new JobInstance()

                         { 

                           Blah   = "xxx",

                           Status = JobInstanceStateEnum.Aborted

                         };

            ji.Status = JobInstanceStateEnum.MissingResources; // The power of implicit operators

            // Act:

            db.JobInstances.Add(ji);

            db.SaveChanges();

// The type 'JobInstanceState' is mapped as a complex type. 

            // The Set method, DbSet objects, and DbEntityEntry objects can only be used with entity types, not complex types.

            // so theres no easy way to navigate to db.JobInstances.First().Status.JobInstanceStateLookup

            // which is good (albiet a leaky abstraction) because Code First doesnt support RO !

            var ji2 = db.JobInstances.First().Status;

            var enumString = JobInstanceStateEnum.MissingResources.ToString();

            var lookup = db.JobInstanceStateLookup.Where(l => l.Value == enumString).FirstOrDefault();

            // Assert:

            Assert.IsTrue(db.JobInstances.Count() > 0);

            Assert.AreEqual(ji2.JobInstanceStateLookupId, lookup.JobInstanceStateLookupId);

        }

Enjoy!

This article is part of the GWB Archives. Original Author: JoshReuben

New on Geeks with Blogs