Automatically Adding Entity Framework Config Classes to the Model Builder (Version 2)

I recently wrote about how to Automatically Add Framework Config Classes to the Model Builder. Since writing that post I have come across a situation where I was writing the exact same model class in a different application. That just didn’t smell right to me, since the other applications data access layer had been packaged up by nuget I could simply add a reference to it. That’s all well and good, but I didn’t want to have to go to any special effort to add the model configuration classes I needed to my modelbuilder. So, I’ve created a fairly simple (although it’s taken me a while to get it working how I want it) base class from which I can derive my context classes.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Base.EntityFramework
{
    public abstract class BaseDbContext : DbContext
    {
        protected BaseDbContext() { }
        protected BaseDbContext(string connectionString) : base(connectionString) { }

        private IEnumerable<Type> GetTypes()
        {
            var type = typeof(EntityTypeConfiguration<>);
            return GetType()
                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p => p.PropertyType.IsGenericType &&
                    p.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>))
                .Select(p => type.MakeGenericType(p.PropertyType.GetGenericArguments().First()))
                .ToArray();
        }

        private static void LoadAllEntityConfigurationsFromAllAssemblies(DbModelBuilder modelBuilder, IEnumerable<Type> types, string assemblyFilter, IEnumerable<string> namePartFilters)
        {
            var path = Path.GetDirectoryName((new Uri(Assembly.GetExecutingAssembly().CodeBase)).AbsolutePath);
            new DirectoryCatalog(path, assemblyFilter)
                .LoadedFiles
                .Where(x =>
                {
                    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(x.ToLower());
                    if (fileNameWithoutExtension != null)
                    {
                        var parts = fileNameWithoutExtension.Split(".".ToCharArray());
                        return parts.Any(part => namePartFilters.Any(namePartFilter => part == namePartFilter.ToLower()));
                    }
                    return false;
                })
                .Select(Assembly.LoadFrom)
                .ToList()
                .ForEach(assembly => assembly.GetTypes()
                .Where(t => types.Contains(t.BaseType))
                .Select(Activator.CreateInstance)
                .ToList<dynamic>()
                .ForEach(instance => modelBuilder.Configurations.Add(instance)));
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            var typesToLoad = GetTypes();
            LoadAllEntityConfigurationsFromAllAssemblies(modelBuilder, typesToLoad, "*.dll", new[] { "Data", "Domain" });
        }

    }
}

and in english ….

  1. Discover which classes for which I need to find and load the relevant EntityTypeConfiguration class. This is done with a little bit of reflection in the GetTypes() method.
  2. Use MEF to find the assemblies the config classes might be in. In this case it’s all of them (*.dll), but that could be changed to whatever is best for your situation.
  3. Use the namePartFilters to narrow down the list of assemblies further. In this case I’m looking for anything with “Data” or “Domain” in the filename (after it’s been split by periods).
  4. Load each assembly.
  5. Load any generic EntityTypeConfiguration types for the model classes used within my context class (the one which is derived from this base class).
  6. Instantiate each config class.
  7. Add it to the modelBuilder’s configurations.

Since this is a base class I can get this behaviour whenever I derive from it ….

public class DomainContext : BaseDbContext, IDomainContext
{
    public IDbSet<Employee> Employees { get; set; }

    public DomainContext() { }
    public DomainContext(string connectionString) : base(connectionString) { }

    IQueryable<Employee> IDomainContext.Employees { get { return Employees; } }
}

What this allows me to do is use models from any assembly I am referencing in my application and just add it to my Context. Any configuration class for it will then be automatically added to the model builder.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s