0

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/

Bruce C
  • 349
  • 1
  • 6
  • 17

1 Answers1

6

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Jon, This did the trick. I did have to change up my CreateDbSet() to handle the fact that DbSet does not have a constructor. I have added the updated code to my original question. Thanks again. – Bruce C Jun 01 '12 at 21:02
  • @JonSkeet in the context of the variable 'dbSetType' in your example. What is 'dbSetType'? My assumption would be 'DbSet<>', but if you attempt to create a 'DbSet' using Activator.CreateInstance() in EF 5.0 you get the following Error: 'Constructon on type not found' – John Hartsock Aug 29 '13 at 19:55
  • @JohnHartsock: It would be the concrete subclass of `DbSet<>`. – Jon Skeet Aug 29 '13 at 19:58
  • @JonSkeet. The concrete subclass of DbSet<> is DBQuery<> and DBQuery dose not have a Constructor – John Hartsock Aug 29 '13 at 20:49
  • @JohnHartsock: Have a look at the OP's edit - it looks like he ended up using the factory approach after all. I've edited my answer to make the first part contingent on an appropriate constructor being available. – Jon Skeet Aug 30 '13 at 05:46