Dynamically Building a Model with Code First

Posted on March 26, 2012. Filed under: Entity Framework, Visual Studio | Tags: , , , , |

I’ve answered a few emails recently on this topic so it felt like time to turn it into a blog post.

In this scenario the set of classes that make up your model isn’t known at compile time and is discovered dynamically at runtime. An example of such a scenario is a WordPress/Orchard/etc. style website that allows modules to be plugged in. These modules live in a separate assembly and may contain classes that represent data to be persisted in the application database. These classes are all pulled into a central Code First model to be used for data access.

 

Finding the Classes

There are lots of different approaches for identifying the classes to be included in the model. For this example lets use a [Persistent] attribute, something like this:

[AttributeUsage(AttributeTargets.Class)]
public class PersistentAttribute : Attribute
{
}

Now we can add some logic to the OnModelCreating method of our context to scan assemblies and add any classes with the [Persist] attribute. We’re going to assume that the assemblies that may contain classes are all loaded into the current AppDomain, of course you may have some other mechanism that provides a list of the assemblies to check.

public class MyContext : DbContext
{
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");

    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
      var entityTypes = assembly
        .GetTypes()
        .Where(t =>
          t.GetCustomAttributes(typeof(PersistentAttribute), inherit: true)
          .Any());

      foreach (var type in entityTypes)
      {
        entityMethod.MakeGenericMethod(type)
          .Invoke(modelBuilder, new object[] { });
      }
    }
  }
}

In this example the entire model is dynamically discovered, but you could also have some static parts of the model that are registered with modelBuilder too.

 

An Alternative Using EntityConfiguration<TEntity>

Code First allows you to create a class that derives from EntityTypeConfiguration<TEntity> to encapsulate the Fluent API configuration for an entity. If your using this approach to configuration you can just look for these configuration classes and register them, instead of finding the entities to register. Notice that we are filtering out the EntityFramework assembly since it has some configuration classes that it uses internally.

public class MyContext : DbContext
{
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    var addMethod = typeof(ConfigurationRegistrar)
      .GetMethods()
      .Single(m => 
        m.Name == "Add" 
        && m.GetGenericArguments().Any(a => a.Name == "TEntityType"));

    foreach (var assembly in AppDomain.CurrentDomain
      .GetAssemblies()
      .Where(a => a.GetName().Name != "EntityFramework"))
    {
      var configTypes = assembly
        .GetTypes()
        .Where(t => t.BaseType != null
          && t.BaseType.IsGenericType
          && t.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));

      foreach (var type in configTypes)
      {
        var entityType = type.BaseType.GetGenericArguments().Single();

        var entityConfig = assembly.CreateInstance(type.FullName);
        addMethod.MakeGenericMethod(entityType)
          .Invoke(modelBuilder.Configurations, new object[] { entityConfig });
      }
    }
  }
}

 

What if the Model Changes?

Code First Migrations to the rescue… You may not be able to use Migrations from the Package Manager Console because the logic to discover your model may require you full application to be running. Fortunately those commands are just thin wrappers over an API. Here is some code to automatically change the database when new classes or properties are added to the model. My recent blog post has more details on invoking Migrations from code.

var config = new DbMigrationsConfiguration<MyContext> { AutomaticMigrationsEnabled = true };
var migrator = new DbMigrator(config);
migrator.Update();

    About

    Rowan works as a Program Manager for the ADO.NET Entity Framework team at Microsoft. He speaks at technical conferences and blogs at romiller.com. Rowan lives in Seattle, Washington with his wife Athalie. Prior to moving to the US he resided in the small state of Tasmania in Australia. Outside of technology Rowan's passions include snowboarding, mountain biking, horse riding, rock climbing and pretty much anything else that involves being active. The primary focus of his life, however, is to follow Jesus.

    RSS

    Subscribe Via RSS

    • Subscribe with Bloglines
    • Add your feed to Newsburst from CNET News.com
    • Subscribe in Google Reader
    • Add to My Yahoo!
    • Subscribe in NewsGator Online
    • The latest comments to all posts in RSS

    Meta

Liked it here?
Why not try sites on the blogroll...

%d bloggers like this: