try-catch-FAIL

Failure is inevitable

NAVIGATION - SEARCH

Adding Entity Framework Code-First Type Configs from a Namespace

A recent project I worked on needed to connect to two different SQL databases using Entity Framework (version 6, not that new EF Core hotness). Out of the box, EF can only bulk-load code-based mappings from an entire assembly, not from a single namespace within an assembly. That didn't work on this project since all of the entities lived in a single assembly. Here's an extension method I wrote to work around this problem.

Brace yourself: thar' be reflection here!!

Loading All Types in an Assembly

For reference, let's take a quick look at how to load code-first mappings from an assembly. You just need to add something like this to your EF Context class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.AddFromAssembly(typeof(SomeEntity).Assembly);
    base.OnModelCreating(modelBuilder);
}

That single line of code will automagically register all of your entities that are defined in the same assembly as SomeEntity.

But, in my case, I had a single assembly that contained entities for two different databases. I could have refactored things into separate assemblies, but I opted for Maximum Effort instead, and used reflection.

Designing the Extension Method

I like to build things top-down, so I started by writing the code as if the extension already existed...

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.AddFromNamespaceContainingType<SomeEntity>();
    base.OnModelCreating(modelBuilder);
}

The idea is that the extension method would look at SomeEntity, find any EF mapping configuration classes that exist in the same namespace only, and add those to modelBuilder.Configurations automagically.

Now that I knew what I needed, I stubbed out the extension method:

public static class EFExtensions
{
    public static void AddFromNamespaceContainingType<TEntityType>(this ConfigurationRegistrar configRegistrar)
        where TEntityType : class
    {
        //TODO: Add the magic!
    }
}

Now it's just a "simple" matter of finding all the entity type configurations.

Finding Configuration Classes

Finding the type configurations doesn't sound difficult. That should be as easy as...

public static class EFExtensions
{
    public static void AddFromNamespaceContainingType<TEntityType>(this ConfigurationRegistrar configRegistrar)
        where TEntityType : class
    {
        var entityType = typeof(TEntityType);
        var configTypes = entityType.Assembly.GetTypes()
            //Uh oh... this won't compile!
            .Where(x => x.Namespace == entityType.Namespace && x is EntityTypeConfiguration<>)
            .ToArray();
    }
}

So yeah, not quite as easy as I had hoped. But hey, this is about MAXIMUM EFFORT, right??

Instead, I had to do some crazy(ish) reflection stuff.

I had the first part right, I do need to just grab all the types from the target entity's assembly:

    var entityType = typeof(TEntityType);
    var configTypes = entityType.Assembly.GetTypes()
        //...working here...

And then I needed to see if the type was in the same namespace as my entity...

    var entityType = typeof(TEntityType);
    var configTypes = entityType.Assembly.GetTypes()
        .Where(x => x.Namespace == entityType.Namespace)
        //...working here...

Then comes the hard part: I need to find out if the type I'm inspecting derives from EntityTypeConfiguration<>:

First, I have to make sure it has a base type...

    var entityType = typeof(TEntityType);
    var configTypes = entityType.Assembly.GetTypes()
        .Where(x => x.Namespace == entityType.Namespace &&
                    x.BaseType != null)
        //...working here...

Then I need to make sure the base type is generic...

    var entityType = typeof(TEntityType);
    var configTypes = entityType.Assembly.GetTypes()
        .Where(x => x.Namespace == entityType.Namespace &&
                    x.BaseType != null &&
                    x.BaseType.IsGenericType)
        //...working here...

And then, finally, I need to see if the base type's generic type definition is EntityTypeConfiguration<>.

    var entityType = typeof(TEntityType);
    var configTypes = entityType.Assembly.GetTypes()
        .Where(x => x.Namespace == entityType.Namespace &&
                    x.BaseType != null &&
                    x.BaseType.IsGenericType &&
                    x.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
        //...working here...

I actually need two bits of information from each type, so I'm going to grab the entity config class type itself, plus the target entity that it applies to.

    var entityType = typeof(TEntityType);
    var configTypeInfo = entityType.Assembly.GetTypes()
        .Where(x => x.Namespace == entityType.Namespace &&
                    x.BaseType != null &&
                    x.BaseType.IsGenericType &&
                    x.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
        .Select(x => new
        {
            ConfigType = x,
            EntityType = x.BaseType.GetGenericArguments()[0]
        })
        .ToArray();

What's that x.BaseType.GetGenericArguments()[0] mess? An entity type configuration class for some entity, let's call it Widget, would have a base class declaration of EntityTypeConfiguration<Widget>. x.BaseType.GetGenericArguments()[0] extracts whatever the entity type is from that declaration, which in this case, would be Widget.

Now that I have the configuration classes, I just need to apply them to my configuration. Should be easy, right? Well...

    foreach (var configInfo in configTypeInfo)
    {
        //Guess what doesn't compile?
        configRegistrar.Add<configInfo.EntityType>(configInfo.ConfigType);
    }

So yeah. That Add method is actually generic. I can't invoke it like this (obviously). Turns out I'm not finished writing reflection yet.

Using Reflection to Apply Configuration Classes

What I actually need to do is get a reference to the closed version of the Add method, then invoke that. I decided to wrap that up in a nice little helper method...

private static void AddConfigToRegistrar(Type configType, Type entityType, ConfigurationRegistrar configRegistrar)
{
    
}

In there, I need to find the Add method, and that means reflection. First, I need to grab all the methods on ConfigurationRegistrar...

    var addMethod = typeof(ConfigurationRegistrar)
        .GetMethods()
        //Working here

Then I need to filter that down to a method named Add...

    var addMethod = typeof(ConfigurationRegistrar)
        .GetMethods()
        .Single(x => x.Name == "Add")
        //Working here

And then I need to find the one that accepts an EntityTypeConfiguration object as a parameter...

    var addMethod = typeof(ConfigurationRegistrar)
        .GetMethods()
        .Single(x => x.Name == "Add" && 
            x.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
        //Working here

That gives me a reference to the open generic method, meaning the method before type parameters have been specified. That open method looks like this: ConfigurationRegistrar.Add<>(). That's still not what I need. I need a closed method. Given a reference to an open generic method, you can create a closed method by calling MakeGenericMethod with the type parameter you want to use (in this case, our entity type):

    var addMethod = typeof(ConfigurationRegistrar)
        .GetMethods()
        .Single(x => x.Name == "Add" && 
            x.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
        .MakeGenericMethod(entityType);

Now I have a reference to ConfigurationRegistrar.Add<EntityType>().

All that's left is to invoke it with an instance of my config class. I'll need to create an instance first...

    var configInstance = Activator.CreateInstance(configType);

And then invoke my closed Add method using that config instance as the parameter:

    addMethod.Invoke(configRegistrar, new [] {configInstance});

It's just that easy!

Putting It All Together

Here's the final version of my extension method:

using System;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Configuration;
using System.Linq;

namespace Heroic.Helpers
{
    public static class EFExtensions
    {
        public static void AddFromNamespaceContainingType<TEntityType>(this ConfigurationRegistrar configRegistrar)
            where TEntityType : class
        {
            var entityType = typeof(TEntityType);
            var configTypeInfo = entityType.Assembly.GetTypes()
                .Where(x => x.Namespace == entityType.Namespace &&
                            x.BaseType != null &&
                            x.BaseType.IsGenericType &&
                            x.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                .Select(x => new
                {
                    ConfigType = x,
                    EntityType = x.BaseType.GetGenericArguments()[0]
                })
                .ToArray();

            foreach (var configInfo in configTypeInfo)
            {
                AddConfigToRegistrar(configInfo.ConfigType, configInfo.EntityType, configRegistrar);
            }
        }

        private static void AddConfigToRegistrar(Type configType, Type entityType, ConfigurationRegistrar configRegistrar)
        {
            var addMethod = typeof(ConfigurationRegistrar)
                .GetMethods()
                .Single(x => x.Name == "Add" && x.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                .MakeGenericMethod(entityType);

            var configInstance = Activator.CreateInstance(configType);

            addMethod.Invoke(configRegistrar, new [] {configInstance});
        }
    }
}

Which can now be used as follows:

public class AppDbContext : DbContext
{
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.AddFromNamespaceContainingType<SomeEntity>();
        base.OnModelCreating(modelBuilder);
    }
}

And There You Have It!

If you ever find yourself needing to split entities between multiple Entity Framework contexts, you can now do so based on namespace, and not just based on assembly. Happy coding!

About Matt Honeycutt...

Matt Honeycutt is a software architect specializing in ASP.NET web applications, particularly ASP.NET MVC. He has over a decade of experience in building (and testing!) web applications. He’s an avid practitioner of Test-Driven Development, creating both the SpecsFor and SpecsFor.Mvc frameworks.

He's also an author for Pluralsight, where he publishes courses on everything from web applications to testing!

blog comments powered by Disqus