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/
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.