4

In my OnModelCreating method for my data context I currently am manually mapping all my entity configuration mapping classes manually, like:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new UserMap());
    // 20 or so mapping configuration below
}

I want to streamline this by using reflection, so I have the following code:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Find all EntityTypeConfiguration classes in the assembly
        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            foreach (Type t in asm.GetTypes())
                if (t.IsDerivedFromOpenGenericType(typeof(EntityTypeConfiguration<>)))
                    modelBuilder.Configurations.Add(Activator.CreateInstance(t));   
    }

the IsDerivedFromOpenGenericType is from this question and works properly.

The problem is this doesn't compile because Activator.CreateInstance(t) returns an object, but the model builder is expecting a System.Data.Entity.ModelConfiguration.ComplexTypeConfiguration<TComplexType>.

Normally when using the Activator class I would just cast the object as whatever I expect type t to be (or what I expect the class to take), but since this is using a generics I don't know of a way to do that.

Does anyone have any ideas?

Community
  • 1
  • 1
KallDrexx
  • 27,229
  • 33
  • 143
  • 254
  • 1
    This "streamlining" is actually wrong approach. It make sense only if you really expect dynamic behavior. If you are only lazy to maintain 20 initializations to make your application clean it is wrong. – Ladislav Mrnka Sep 21 '11 at 13:04
  • Right now the current count is 25 entity configuration classes, and as I add more functionality this can easily get bigger and bigger. – KallDrexx Sep 21 '11 at 13:11
  • Yes but still it is a static set so it should not be a problem to maintain it or at least to limit assemblies used to load configurations. This way you never know what will be loaded. – Ladislav Mrnka Sep 21 '11 at 13:36
  • True but it's also easy to forget to add a mapping when you are adding a few at a time. – KallDrexx Sep 21 '11 at 13:37
  • 1
    You will know it immediately once you run your new feature but the fact that you have some additional or wrong mappings can be more dangerous - especially if you have initialization strategy which will delete your database when model changes. – Ladislav Mrnka Sep 21 '11 at 13:46

4 Answers4

9

I'm not sure why this information is so hard to find (at least it was for me), but there is a much simpler way to do it detailed here.

public class MyDbContext : DbContext
{
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Configurations.AddFromAssembly(Assembly.GetAssembly(GetType())); //Current Assembly
    base.OnModelCreating(modelBuilder);
  }
}
mellis481
  • 4,332
  • 12
  • 71
  • 118
5

I got this from Rowan Miller at Microsoft:

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {

        var addMethod = typeof (ConfigurationRegistrar)
            .GetMethods()
            .Single(m =>
                    m.Name == "Add" &&
                    m.GetGenericArguments().Any(a => a.Name == "TEntityType"));

        var assemblies = AppDomain.CurrentDomain
            .GetAssemblies()
            .Where(a => a.GetName().Name != "EntityFramework");

        foreach (var assembly in assemblies)
        {
            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[] {entityConfig});
            }
        }

        base.OnModelCreating(modelBuilder);
    }
DaveShaw
  • 52,123
  • 16
  • 112
  • 141
Latin Warrior
  • 902
  • 11
  • 14
2

I found a much better way of doing this using composition with MEF:

public class Album
{
    public int AlbumId { get; set; }
    public int GenreId { get; set; }
    public int ArtistId { get; set; }
    public string Title { get; set; }
    public decimal Price { get; set; }
    public string AlbumArtUrl { get; set; }
    public Genre Genre { get; set; }
    public Artist Artist { get; set; }
}

using System.Data.Entity.ModelConfiguration.Configuration;

namespace MvcMusicStore.Models
{
   public interface IEntityConfiguration
   {
       void AddConfiguration(ConfigurationRegistrar registrar);
   }
}

using System.ComponentModel.Composition;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Configuration;

namespace MvcMusicStore.Models.TypeConfig
{
  [Export(typeof(IEntityConfiguration))]
  public class AlbumTypeConfiguration : EntityTypeConfiguration<Album>, IEntityConfiguration
  {
    public AlbumTypeConfiguration()
    {
        ToTable("Album");
    }

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

using System.Collections.Generic;
using System.ComponentModel.Composition;

namespace MvcMusicStore.Models
{
  public class ContextConfiguration
  {
      [ImportMany(typeof(IEntityConfiguration))]
      public IEnumerable<IEntityConfiguration> Configurations { get; set; }
  }
}

 using System;
 using System.ComponentModel.Composition.Hosting;
 using System.Data.Entity;
 using System.Data.Entity.ModelConfiguration;
 using System.Data.Entity.ModelConfiguration.Configuration;
 using System.Linq;
 using System.Reflection;

 namespace MvcMusicStore.Models
 {
   public class MusicStoreEntities : DbContext
   {
    public DbSet<Album> Albums { get; set; }
    public DbSet<Genre> Genres { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
        var container = new CompositionContainer(catalog);
        var exports = container.GetExportedValues<IEntityConfiguration>();

        foreach (var entityConfiguration in exports)
        {
            entityConfiguration.AddConfiguration(modelBuilder.Configurations);
        }    
        base.OnModelCreating(modelBuilder);
    }
  }
}

credit: OdeToCode.com Composing Entity Framework Fluent Configurations

Latin Warrior
  • 902
  • 11
  • 14
1

You have to use reflection here as well.

Get the Method with Type.GetMethod() and then create the generic version you need with MethodInfo.MakeGenericMethod():

Type tCmpxTypeConfig = typeof (EntityTypeConfiguration<>);
tCmpxTypeConfig = tCmpxTypeConfig.MakeGenericType(t);

modelBuilder.Configurations.GetType()
    .GetMethod("Add", new Type[] { tCmpxTypeConfig })
    .MakeGenericMethod(t)
    .Invoke(modelBuilder.Configurations, 
        new object[] { Activator.CreateInstance(t) });
Jan
  • 15,802
  • 5
  • 35
  • 59
  • The Invoke call is wrong, it looks like it should be `.Invoke(modelBuilder.Configurations, new object[] { Activator.CreateInstance(t) });`. However this is giving me an `AmbiguousMatchException`, I assume because there are 2 overloads for the method. – KallDrexx Sep 21 '11 at 13:06
  • Ah yes, you are right. You have to specify which overload of the `Add` method you want. I have updated my code. Give it a shot. – Jan Sep 21 '11 at 13:13
  • That didn't work, GetMethod() returns null. After some googling I think it's because the Add method uses generics, and according to http://blogs.msdn.com/b/yirutang/archive/2005/09/14/466280.aspx it seems like GetMethod doesn't work with generics for now and you have to do some code to work around it. – KallDrexx Sep 21 '11 at 13:28
  • Ok didn't know that. Mayby this answer is helpful for working around: http://stackoverflow.com/questions/4035719/getmethod-for-generic-method/4036187#4036187 – Jan Sep 21 '11 at 13:33
  • Judging from the accepted answer noting faults with that answer, I guess I should just maintain a manual list and forget about reflection for now. – KallDrexx Sep 21 '11 at 13:41