Custom Entity Type Configurations in Entity Framework Code First (Part 2)

In my last post I discussed how to inherit from the EntityTypeConfiguration class and use reflection to dynamically configure Entity Framework. In this post I’ll expand on that technique by using a custom interface, reflection, and several helper classes to automatically apply Entity Framework configurations from arbitrary classes.

The first step is to revisit the EntityTypeConfiguration<TEntityType> that is part of Entity Framework. Recall that by overriding it you can provide your own configurations:

public class DepartmentTypeConfiguration : EntityTypeConfiguration<Department>
{
  public DepartmentTypeConfiguration()
  {
    Property(t => t.Name).IsRequired();
  }
}

Once you’re done, the EntityTypeConfiguration class is registered with the ConfigurationRegistrar. One problem with this is that the ConfigurationRegistrar only accepts one EntityTypeConfiguration per type. To get around this, instead of creating derived EntityTypeConfiguration classes we’re going to create a custom interface that can be implemented to apply configuration to a common pre-generated EntityTypeConfiguration class per type. This way, even if there are multiple configurations for a given entity type, they’ll all get applied to the same EntityTypeConfiguration instance. The first step is defining the interface that our own configuration classes will implement:

// This interface is needed to refer to the closed generic without the generic
// type parameter being available
public interface IEntityTypeConfiguration
{
}

// Implement this interface to enable dynamic entity configuration 
public interface IEntityTypeConfiguration<TEntity>
  : IEntityTypeConfiguration where TEntity : class
{
  void Configure(EntityTypeConfiguration<TEntity> configuration);
}

The first non-generic version of the interface is only needed so that we can refer to the implementation without requiring the generic type parameter to be specified. The generic interface is the one that should be implemented to provide entity configuration data. Notice how instead of accessing an EntityTypeConfiguration by inheriting from it, we are now passing it in through the Configure method. This results in configuration code that looks like:

public class Department 
{

  // ... Department entity data goes here

  // This is our entity configuration code
  // I prefer to use a nested class to keep everything together, but you don't have to
  public class DepartmentTypeConfiguration : IEntityTypeConfiguration<Department>
  {
    void Configure(EntityTypeConfiguration<Department> configuration)
    {
      configuration.Property(t => t.Name).IsRequired();
    }
  }
}

The rest of the code that we need is all just rigging code to get this technique to work. The first helper class we’ll need allows us to add an EntityTypeConfiguration to the ConfigurationRegistrar without knowing it’s generic parameter types. This is needed because ConfigurationRegistrar.Add<TEntity>() is a generic method and otherwise wouldn’t let us add an open generic instance. If this isn’t making much sense yet, hang in there. Hopefully it will by the time I’m done.

// Similar to above, this lets us call AddConfiguration() without knowing the
// generic type parameter
public interface IAutomaticEntityTypeConfiguration
{
  void AddConfiguration(ConfigurationRegistrar registrar);
}

// A derived EntityTypeConfiguration that can add itself to the ConfigurationRegistrar
public class AutomaticEntityTypeConfiguration<TEntity>
  : EntityTypeConfiguration<TEntity>, IAutomaticEntityTypeConfiguration
  where TEntity : class
{
  public AutomaticEntityTypeConfiguration()
  {
  }

  public void AddConfiguration(ConfigurationRegistrar registrar)
  {
    registrar.Add(this);
  }
}

Finally, the last class we’ll need ties the user code (IEntityTypeConfiguration<TEntity>) and the EntityTypeConfiguration implementation (AutomaticEntityTypeConfiguration<TEntity>) together.

public interface IEntityTypeConfigurationAdapter
{
  void Configure(IEntityTypeConfiguration configurationInterface,
    IAutomaticEntityTypeConfiguration configuration);
}

public class EntityTypeConfigurationAdapter<TEntity>
  : IEntityTypeConfigurationAdapter where TEntity : class
{
  public void Configure(IEntityTypeConfiguration configurationInterface,
    IAutomaticEntityTypeConfiguration configuration)
  {
    IEntityTypeConfiguration<TEntity> typedConfigurationInterface
      = (IEntityTypeConfiguration<TEntity>)configurationInterface;
    typedConfigurationInterface.Configure((EntityTypeConfiguration<TEntity>)configuration);
  }
}

Now that we have all of the needed classes defined, I can show you the code I have in my DbContext to find, configure, and register entity type configurations.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{

  // Get all entity type configurations
  Dictionary<Type, List<IEntityTypeConfiguration>> typeConfigurations
    = GetTypeConfigurations();

  // Add all the type configurations
  foreach(KeyValuePair<Type, List<IEntityTypeConfiguration>> kvp in typeConfigurations)
  {
    // Create an automatic entity type configurator
    Type configType = typeof(AutomaticEntityTypeConfiguration<>).MakeGenericType(kvp.Key);
    IAutomaticEntityTypeConfiguration configuration
      = (IAutomaticEntityTypeConfiguration)Activator.CreateInstance(configType);

    // Apply the configurations
    ApplyConfigurations(kvp.Key, kvp.Value, configuration);                

    // Add the configuration to the registry
    configuration.AddConfiguration(modelBuilder.Configurations);
  }

}

The GetTypeConfigurations method uses reflection to scan the same assembly as your DbContext, find all IEntityTypeConfiguration implementers, instantiate them, and record them in a list-per-entity-type. Note that this code uses an AddMulti extension method for working with multi-value dictionaries. You could just as easily expand that part of the code to check if there is already a List<> available and create one if not.

private Dictionary<Type, List<IEntityTypeConfiguration>> GetTypeConfigurations()
{  
  Dictionary<Type, List<IEntityTypeConfiguration>> typeConfigurations
    = new Dictionary<Type, List<IEntityTypeConfiguration>>();
  var typesToRegister = Assembly.GetAssembly(typeof(YourDbContext)).GetTypes()
    .Where(type => type.Namespace != null
      && type.Namespace.Equals(typeof(YourDbContext).Namespace))
    .Where(type => type.BaseType.IsGenericType
      && type.BaseType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>));
  foreach (var configurationType in typesToRegister)
  {
    IEntityTypeConfiguration configurationInstance
      = (IEntityTypeConfiguration)Activator.CreateInstance(configurationType);
    foreach (Type entityType in configurationType
      .GetGenericInterfaces(typeof(IEntityTypeConfiguration))
      .Select(i => i.GetGenericArguments()[0]))
      {
        typeConfigurations.AddMulti(entityType, configurationInstance);
      }
   {
}

You’ll also need the following extension method somewhere:

// This returns all generic interfaces implemented by a given type that themselves implement a given base interface
public static IEnumerable<Type> GetGenericInterfaces(this Type type, Type baseInterfaceType)
{
    Validate.That(
        type.IsNot().Null(() => type),
        baseInterfaceType.IsNot().Null(() => baseInterfaceType));
    return type.GetInterfaces().Where(i => i.IsGenericType && baseInterfaceType.IsAssignableFrom(i));
}

And finally, the ApplyConfigurations method just uses the EntityTypeConfigurationAdapter to tie the IEntityTypeConfiguration and IAutomaticEntityTypeConfiguration classes together.

private void ApplyConfigurations(
  Type entityType,
  IList<IEntityTypeConfiguration> typeConfigurationInterfaces,
  IAutomaticEntityTypeConfiguration configuration)
{
  // Construct an adapter that will help call the typed configuration methods
  Type adapterType = typeof(EntityTypeConfigurationAdapter<>).MakeGenericType(entityType);
  IEntityTypeConfigurationAdapter adapter
    = (IEntityTypeConfigurationAdapter)Activator.CreateInstance(adapterType);

  // Iterate type configuration interfaces and add to the actual configuration
  foreach (IEntityTypeConfiguration typeConfigurationInterface
    in typeConfigurationInterfaces)
  {
    adapter.Configure(typeConfigurationInterface, configuration);
  }
}

Phew. That was a lot of code. Hopefully it made some sense. I realize it was pretty deep and might not have gelled just by reading it in blog format. The best advice I can give is that if this seems like a capability you want or need that you copy to code into some source files, get it to compile, and step through it. That should give you a better understanding of how it all fits together.

Advertisements