4

I'm trying to get all the implementations of IEntityModelBuilder with the following code, but instead it returns an empty collection.

public class EntityFrameworkDbContext : DbContext
{
    //constructor(s) and entities DbSets...

    private static IEnumerable<IEntityModelBuilder<IEntity>> _entitymodelBuilders;
    internal IEnumerable<IEntityModelBuilder<IEntity>> EntityModelBuilders
    {
        get
        {
            if (_entitymodelBuilders == null)
            {
                var type = typeof(IEntityModelBuilder<IEntity>);

                _entitymodelBuilders = Assembly.GetAssembly(type).GetTypes()
                    .Where(t => type.IsAssignableFrom(t) && t.IsClass)
                    .Select(t => (IEntityModelBuilder<IEntity>)Activator.CreateInstance(t, new object[0]));
            }

            return _entitymodelBuilders;
        }
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        foreach (var builder in EntityModelBuilders)
            builder.Build(modelBuilder);

        base.OnModelCreating(modelBuilder);
    }
}

internal interface IEntityModelBuilder<TEntity> where TEntity : IEntity
{
    void Build(DbModelBuilder modelBuilder);
}

//sample implementation
internal class UserModelBuilder : IEntityModelBuilder<User>
{
    public void Build(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .ToTable("users")
            .HasKey(e => e.Id);

        modelBuilder.Entity<User>()
            .Property(e => e.Id)
            .HasColumnName("id");

        modelBuilder.Entity<User>()
            .Property(e => e.Email)
            .HasColumnName("email");

        //and so on...
    }
}

If I change the type with

var type = typeof(IEntityModelBuilder<User>);

the types fetching code runs fine and returns the expected UserModelBuilder. How can I do this with generics?

lucacelenza
  • 1,259
  • 1
  • 15
  • 28

2 Answers2

13

Although Slava's solution works, it isn't, in general, completely safe because of Contains. It is possible that some other interface/type could contain the name of the interface you are searching for. In this case, imagine you have another interface named IEntityModelBuilderHelper.

Also, with very little effort you can generalize this code to be much more poweful. Consider the following two methods:

public static IEnumerable<Type> GetAllTypes(Type genericType)
{
    if (!genericType.IsGenericTypeDefinition)
        throw new ArgumentException("Specified type must be a generic type definition.", nameof(genericType));

    return Assembly.GetExecutingAssembly()
                   .GetTypes()
                   .Where(t => t.GetInterfaces()
                                .Any(i => i.IsGenericType &&
                                     i.GetGenericTypeDefinition().Equals(genericType)));
}

And,

public static IEnumerable<Type> GetAllTypes(Type genericType, params Type[] genericParameterTypes)
{
    if (!genericType.IsGenericTypeDefinition)
        throw new ArgumentException("Specified type must be a generic type definition.", nameof(genericType));

    return Assembly.GetExecutingAssembly()
                   .GetTypes()
                   .Where(t => t.GetInterfaces()
                                .Any(i => i.IsGenericType &&
                                          i.GetGenericTypeDefinition().Equals(genericType) &&
                                          i.GetGenericArguments().Count() == genericParameterTypes.Length &&
                                          i.GetGenericArguments().Zip(genericParameterTypes, 
                                                                      (f, s) => s.IsAssignableFrom(f))
                                                                 .All(z => z)));
}

The former will give you all types that implement the supplied generic type definition, that is typeof(MyGenericType<>), with no constrain whatsoever on the generic type parameter. The latter will do the same thing but with the supplied type constraints.

Consider the following types:

public interface IFoo<T> { }
public interface IEntity { }
public class A : IEntity { }

public class Foo : IFoo<IEntity> { }
public class FooA : IFoo<A> { }
public class FooS : IFoo<string> { }

var types = GetAllTypes(typeof(IFoo<>)); will return 3 types: { Foo, FooA, FooS } while var types = GetAllTypes(typeof(IFoo<>), typeof(IEntity)); will return only two types: { Foo, FooA }.

InBetween
  • 32,319
  • 3
  • 50
  • 90
7

You can try working example.

Declarations:

public interface IEntity { }
public class Entity1 : IEntity { }
public class Entity2 : IEntity { }

public interface IEntityModelBuilder<out T> where T : IEntity { }

public class BaseClass1 : IEntityModelBuilder<Entity1>
{        
    public BaseClass1(int a) { }
}
public class BaseClass2 : IEntityModelBuilder<Entity2>
{
    public BaseClass2(int a) { }
}

Usage:

List<IEntityModelBuilder<IEntity>> objects = Assembly.GetExecutingAssembly().GetTypes()
    .Where(x => x.GetInterfaces().Any(y => y.IsGenericType && && y.Name == "IEntityModelBuilder`1"))
    .Select(x => (IEntityModelBuilder<IEntity>)Activator.CreateInstance(x, new object[] { 0 })).ToList();
Slava Utesinov
  • 13,410
  • 2
  • 19
  • 26
  • Works like a charm, but Is it possible to get an instance for each and every of the types returned with Activator.GetInstance? – lucacelenza Feb 15 '17 at 09:34
  • Getting closer: how can I stop the compiler whining about the implicit conversion (i.e. _entitymodelBuilders = (IEntityModelBuilder)objects;)? – lucacelenza Feb 15 '17 at 09:52
  • 2
    This solution is, in general, fragile. You can theoretically have a name conflict (`IEntityModelBuilderHelper`) and get erronous results. If there was no other way I'd do this, but there are safer solutions available. – InBetween Feb 15 '17 at 10:22
  • @LucaCelenza, I updated answer. What you want (conversion ) can be done only at case of covariation(**out** keyword) of `IEntityModelBuilder` – Slava Utesinov Feb 15 '17 at 10:40
  • A slight improvement would be to utilise the open generic type `typeof(Foo<>)` instead of a closed type `typeof(Foo)` to explicitly get the type you intend to without conflict. Open generics are fine in reflection. – Adam Houldsworth Feb 15 '17 at 10:54
  • I liked @InBetween 's answer more and - although it's the solution I ended up to implement - the "covariance part" pointed out by Slava was what showed me the right direction and helped me get over the casting problem. – lucacelenza Feb 16 '17 at 10:45