Search code examples
c#genericstdddatacontext

How to create instance of class by type with a generic


I am having problems creating a new instance of a class by type with a generic. What I am trying to do is have a database context that can be created with either a DBSet<> or a FakeDBSet<>. The FakeDBSet would be used in test code. I currently have a complete fake datacontext but it is a waste since the only real difference is the DBSet used. I have looked into using the Activator.CreateInstance() without much luck.

Example:

public class Album {}
public class Artist {}

public class MusicStoreContext
{
    public IDbSet<Album> Albums { get; set; }
    public IDbSet<Artist> Artists { get; set; }

    public MusicStoreContext(Type dbSetType)
    {
        Albums = new (dbSetType)<Album>;
        Artists = new (dbSetType)<Artist>;
    }

}

public class Startup
{
    public Startup()
    {
        // Production code would do something like this:
        MusicStoreContext context = new MusicStoreContext(typeof(DbSet<>));

        // Test code would do something like this:
        MusicStoreContext testContext = new MusicStoreContext(typeof(FakeDbSet<>));
    }
}

I have also tried something like this:

public class MusicStoreContext<T> where T : IDBSet
{
    public IDbSet<Album> Albums { get; set; }
    public IDbSet<Artist> Artists { get; set; }
    ...

Here is what I came up with that works thanks to Jon's suggestion:

public class MusicStoreContext
{
    private IDbSet<Album> _Albums;
    private IDbSet<Artist> _Artists;

    public IDbSet<Album> Albums { get {return _Albums;} }
    public IDbSet<Artist> Artists { get {return _Artists; }

    public MusicStoreContext(Type dbSetType)
    {
        Albums = new (dbSetType)<Album>;
        Artists = new (dbSetType)<Artist>;
    }

    public TaxDocumentsContext() : base() 
    {
        CreateDbSets(new ProductionDbSetProvider());
    }

    public TaxDocumentsContext(IDbSetProvider provider)
    {
        CreateDbSets(provider);
    }

    private void CreateDbSets(IDbSetProvider provider)
    {
        provider.CreateDbSet<Album>(this, ref _Albums);
        provider.CreateDbSet<Artist>(this, ref _Artists);
    }

}

And for the DbSetProvider:

public interface IDbSetProvider
{
    void CreateDbSet<T>(DbContext context, ref IDbSet<T> dbSet) where T : class;
}

public class FakeDbSetProvider : IDbSetProvider
{
    public void CreateDbSet<T>(DbContext context, ref IDbSet<T> dbSet) where T : class
    {
        dbSet = new FakeDbSet<T>();
    }

}

public class ProductionDbSetProvider : IDbSetProvider
{
    public void CreateDbSet<T>(DbContext context, ref IDbSet<T> dbSet) where T : class
    {
        dbSet = context.Set<T>();
    }
}

Now I am able to easily test without hitting the DB using a FakeDbSet from here: http://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic-repository/


Solution

  • EDIT: If the set in question had an appropriate constructor, you could use Type.MakeGenericType to create the relevant constructed type and call the constructor using Activator.CreateInstance:

    Type albumType = dbSetType.MakeGenericType(typeof(Album));
    Albums = (IDbSet<Album>) Activator.CreateInstance(albumType);
    

    Alternatively, you could pass in a DbSetProvider (or whatever) which had a generic method:

    public IDbSet<T> CreateDbSet<T>()
    

    then you'd have a ProductionDbSetProvider and a FakeDbSetProvider:

    public MusicStoreContext(DbSetProvider provider)
    {
        Albums = provider.CreateDbSet<Album>();
        Artists = provider.CreateDbSet<Artist>();
    }
    

    Personally that feels cleaner to me, but YMMV.