Jasinski Technical Wiki

Navigation

Home Page
Index
All Pages

Quick Search
»
Advanced Search »

Contributor Links

Create a new Page
Administration
File Management
Login/Logout
Your Profile

Other Wiki Sections

Software

PoweredBy

Extension Method to Seed Enum Values into Database Table - Entity Framework Code First

RSS
Modified on Wed, Feb 03, 2021, 3:15 PM by Administrator Categorized as Entity Framework Code First

Overview

When an enum is persisted in a database table, it is better if the Code First entity doesn't use a (SQL Server) IDENTITY field. This is recommended for two reasons.

  • Having an IDENTITY field on an enum table requires the use of IDENTITY_INSERT, which Entity Framework doesn't play with well.
  • Changing from an IDENTITY field to a non-IDENTITY field is a troublesome endeavor in SQL Server. It requires the creation of a temporary table, the copying of data between tables, and a few table renames.

For these reasons, it is useful to have a base class to support the creation and seeding of enum tables in Entity Framework.

References

The AddSpaces extension method can be found here.

Reusable Code - EF6

Add this code within your DAL project/namespace.

public abstract class EnumDbEntity<TEnum>
        where TEnum : struct, IConvertible
{
    [Required]
    public TEnum Id { get; set; }

    [Required, StringLength(100)]
    public string DisplayName { get; set; }

    /// <summary>
    /// TODO: Override this method where needed, especially if the derived class has extra fields.
    /// </summary>
    /// <param name="value"></param>
    /// <param name="addSpaces"></param>
    /// <returns></returns>
    public virtual EnumDbEntity<TEnum> InitForSeeding(TEnum value, bool addSpaces = true)
    {
        Id = value;
        DisplayName = EnumHelper<TEnum>.GetDisplayValue(value, addSpaces);
        return this;
    }

}

public static class EnumDbEntityExtensions
{
    public static void SeedEnumTable<TEnum, TEntity>(this IDbSet<TEntity> dbSet, bool addSpaces = true)
        where TEnum : struct, IConvertible
        where TEntity : EnumDbEntity<TEnum>, new()
    {
        if (!typeof(TEnum).IsEnum) throw new ArgumentException("TEnum must be an enumerated type");

        var itemValues = (TEnum[])Enum.GetValues(typeof(TEnum));

        foreach (var itemValue in itemValues)
        {
            var item = new TEntity();
            item.InitForSeeding(itemValue, addSpaces);
            dbSet.AddOrUpdate(a => a.Id, item);
        }
    }
}


/* Reference
    * https://stackoverflow.com/questions/13099834/how-to-get-the-display-name-attribute-of-an-enum-member-via-mvc-razor-code */
public static class EnumHelper<T>
    where T : struct, IConvertible
{

    public static string GetDisplayValue(T value, bool addSpaces = true)
    {
        var ret = String.Empty;
        var fieldInfo = value.GetType().GetField(value.ToString());

        if (fieldInfo == null)
        {
            return "Display Name Unknown";
        }

        var dispAttrib = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];

        if (dispAttrib != null)
        {
            if (dispAttrib.Length > 0)
            {
                ret = dispAttrib[0].Name;
            }
            else
            {
                ret = value.ToString();

                if (addSpaces)
                {
                    ret = ret.AddSpaces();
                }
            }
        }
        return ret;
    }
}

Implementation Sample Code - EF6

Enum and Database Entity

public enum OrderStatusEnum
{
    Active,
    Inactive
}

public class OrderStatus : EnumDbEntity<OrderStatusEnum>
{ }

Seeding the Table - EF6

context.OrderStatuses.SeedEnumTable<OrderStatusEnum, OrderStatus>();

Reusable Code - EF Core

using Microsoft.EntityFrameworkCore;
using NetSoft.iPathPro.Common;
using System;
using System.ComponentModel.DataAnnotations;

public abstract class EnumDbEntity<TEnum>
        where TEnum : struct, IConvertible
{
    [Required]
    public TEnum Id { get; set; }

    [Required, StringLength(100)]
    public string DisplayName { get; set; }

    /// <summary>
    /// TODO: Override this method where needed, especially if the derived class has extra fields.
    /// </summary>
    /// <param name="value"></param>
    /// <param name="addSpaces"></param>
    /// <returns></returns>
    public virtual EnumDbEntity<TEnum> InitForSeeding(TEnum value, bool addSpaces = true)
    {
        Id = value;
        DisplayName = EnumHelper<TEnum>.GetDisplayValue(value, addSpaces);
        return this;
    }

}

public static class EnumDbEntityExtensions
{
    /*--- EF Core Version ---*/
    /* Usage: within DbContext's "protected override void OnModelCreating(ModelBuilder mb)" add the */
    /* following code: mb.SeedEnumTable<TEnum, TEntity>(); */
    /* Be sure to subsitute your enum and entity class for TEnum and TEntity, respectively. */
    public static void SeedEnumTable<TEnum, TEntity>(this ModelBuilder mb, bool addSpaces = true)
        where TEnum : struct, IConvertible
        where TEntity : EnumDbEntity<TEnum>, new()
    {
        if (!typeof(TEnum).IsEnum) throw new ArgumentException("TEnum must be an enumerated type");

        var itemValues = (TEnum[])Enum.GetValues(typeof(TEnum));

        foreach (var itemValue in itemValues)
        {
            var item = new TEntity();
            item.InitForSeeding(itemValue, addSpaces);
            mb.Entity<TEntity>().HasData(item);
        }
    }

    private static void SeedEnumTable<TEnum, TEntity>(ModelBuilder mb, TEnum pEnum, TEntity pEntity, bool addSpaces = true)
        where TEnum : struct, IConvertible
        where TEntity : EnumDbEntity<TEnum>, new()
    {
        mb.SeedEnumTable<TEnum, TEntity>(addSpaces);
    }

    public static void SeedAllEnumTables(
        this ModelBuilder mb,
        IEnumerable<Type> excludedTypes,
        bool addSpaces = true)
    {
        /*--- Get DAL Assembly ---*/
        var asm = Assembly.GetExecutingAssembly();

        var types = asm.DefinedTypes
            .Where(x =>
                x.BaseType != null
                && x.BaseType.IsGenericType
                && x.BaseType.Name.StartsWith("EnumDbEntity")
                && !excludedTypes.Contains(x)
                )
            .OrderBy(x => x.Name)
            .ToList();

        /*--- Loop Through all Types based on EnumDbEntity<TEnum> ---*/
        foreach (var type in types)
        {
            /*--- Get Instance of any Enum value ---*/
            var enumType = type.BaseType.GenericTypeArguments[0];

            var enumNames = Enum.GetNames(enumType);

            /*--- Skip Empty Enums ---*/
            if (enumNames.Length == 0)
            {
                continue;
            }

            dynamic pEnum = Enum.Parse(enumType, enumNames[0]);

            /*--- Create Instance of EntityType ---*/
            dynamic pEntity = Activator.CreateInstance(type);

            /*--- Seed the Table ---*/
            SeedEnumTable(mb, pEnum, pEntity, addSpaces);
        }
    }
}


/* Reference
    * https://stackoverflow.com/questions/13099834/how-to-get-the-display-name-attribute-of-an-enum-member-via-mvc-razor-code */
public static class EnumHelper<T>
    where T : struct, IConvertible
{

    public static string GetDisplayValue(T value, bool addSpaces = true)
    {
        var ret = String.Empty;
        var fieldInfo = value.GetType().GetField(value.ToString());

        if (fieldInfo == null)
        {
            return "Display Name Unknown";
        }

        var dispAttrib = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];

        if (dispAttrib != null)
        {
            if (dispAttrib.Length > 0)
            {
                ret = dispAttrib[0].Name;
            }
            else
            {
                ret = value.ToString();

                if (addSpaces)
                {
                    ret = ret.AddSpaces();
                }
            }
        }
        return ret;
    }
}

Implementation Sample Code - EF Core

Enum and Database Entity

public enum OrderStatusEnum
{
    Active,
    Inactive
}

public class OrderStatus : EnumDbEntity<OrderStatusEnum>
{ }

Seeding the Table - EF Core

Example 1

protected override void OnModelCreating(ModelBuilder mb)
{
    // TODO - Repeat the following line for each EnumDbEntity implementation
    mb.SeedEnumTable<OrderStatusEnum, OrderStatus>();
}

Example 2

protected override void OnModelCreating(ModelBuilder mb)
{
    // The next line seed every database table where the entity inherits from EnumDbEntity<TEnum>
    // EXCEPT for those types listed in the "excludedTypes" array.  These types are excluded because
    // they require special handling.
    mb.SeedAllEnumTables(new[] {
        // TODO - Repeat the following line for each special case
        typeof(MyEntity) 
    });

    // TODO - Repeat the following line for each special case
    mb.SeedEnumTable<MyEntityEnum, MyEntity>(false);
}

ScrewTurn Wiki version 3.0.1.400. Some of the icons created by FamFamFam. Except where noted, all contents Copyright © 1999-2024, Patrick Jasinski.