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
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 the 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.