Reputation: 15345
We have an abstract generic class in C#, very much like this:
public abstract class Repository<T>
where T: Entity
{
public abstract void Create(T t);
public abstract T Retrieve(Id id);
//etc.
}
We have a few derived classes, such as:
public class EventRepository
: Repository<Event>
{
//etc.
}
We are implementing a unit of work pattern that keeps a dictionary to map entity types to repository types, so that when an entity needs to be created or changed, it knows what repository to instantiate:
private Dictionary<Type, Type> m_dicMapper;
This dictionary is initialised and loaded with all the mappings, like this:
m_dicMapper.Add(typeof(Event), typeof(EventRepository));
//and so on for a few other repository classes.
Then, when an entity e
needs to be created, for example:
//retrieve the repository type for the correct entity type.
Type tyRepo = m_dicMapper[e.GetType()];
//instantiate a repository of that type.
repo = Activator.CreateInstance(tyRepo);
//and now create the entity in persistence.
repo.Create(e);
The problem is, what type is repo
in the code above? I would like to declare it as of generic Repository<T>
type, but apparently C# won't let me do it. None of the following lines compile:
Repository repo;
Repository<T> repo;
Repository<e.GetType()> repo;
I can declare it as var
, but then I don't get access to the Create
and other methods that Repository<T>
implements. I was hoping to be able to use the generic class to, well, use repositories generically! But I guess I am doing something wrong.
So my question is, what workarounds of coding and/or design could I use to solve this problem? Thank you.
Upvotes: 2
Views: 959
Reputation: 15345
After much trying, I have found a solution to this. Basically, the problem was that I have a collection of things (the various repositories) that I want to treat similarly, from an abstract point of view, but they do not inherit from a common class; they are only particular reifications of a generic class, but that does not help much. Unless...
We use a repository registry class, as suggested by @Richard, having a method like this:
public Repository<T> GetRepository<T>(T t)
where T : Entity
{
Type tyRepo = m_dic[t.GetType()];
Repository<T> repo = (Repository<T>)Activator.CreateInstance(tyRepo);
return repo;
}
Note that the t
argument is needed because the dictionary must be searched by t.GetType()
rather than typeof(T)
, in order to consider the run-time (rather than compile-time) type of the entity. If we don't do this, all entities passed are treated as being of the root type Entity
.
Similarly, the usage of the registry needs to take this into account. When invoking the method, dynamic
must be used to allow the run-time to determine the type of the entity as needed, rather than fixing it at compile-time. The following code fails:
Event ev = new Event();
Entity e = ev;
var repo = registry.GetRepository(e);
because e, despite being an Event
, is resolved at compile-time as an Entity
as far as generic resolution is concerned. You need something like this:
Event ev = new Event();
dynamic e = ev;
var repo = registry.GetRepository(e);
And this solves my problem.
Thanks to @Richard for his suggestion to create a separate repository registry, and to everyone else who have provided feedback.
Upvotes: 0
Reputation: 8290
I would personally recommend encapsulating your access to the dictionary with a separate singleton class that then wraps your dictionary getters and setters, something like a RepositoryStore
class.
I'd recommend changing Dictionary<Type, Type>
to Dictionary<Type, object>
, and then handle the casting within the RepositoryStore
; something like so?
Type
s and Lazy<T>
)If you're using .NET 4, you could take full advantage of the Lazy
class, and change the dictionary type to be IDictionary<Type, Lazy<object>>
. I've amended my original answer to reflect how this might work:
class RepositoryStore
{
private IDictionary<Type, Lazy<object>> Repositories { get; set; }
public RepositoryStore()
{
this.Repositories = new Dictionary<Type, Lazy<object>>();
}
public RepositoryStore Add<T, TRepo>() where TRepo : Repository<T>
{
this.Repositories[typeof(T)] = new Lazy<object>(() => Activator.CreateInstance(typeof(TRepo)));
return this;
}
public Repository<T> GetRepository<T>()
{
if (this.Repositories.ContainsKey(typeof(T)))
{
return this.Repositories[typeof(T)].Value as Repository<T>;
}
throw new KeyNotFoundException("Unable to find repository for type: " + typeof(T).Name);
}
}
The usage is then pretty simple...
var repositoryStore = new RepositoryStore()
// ... set up the repository store, in the singleton?
Repository<MyObject> myObjectRepository = repositoryStore.GetRepository<MyObject>();
myObjectRepository.Create(new MyObject());
Upvotes: 2
Reputation: 245046
If you know the type you want at compile time, then you can use generics. For example, you could do something like:
m_dictMapper.Add<Event, EventRepository>();
…
var repo = m_dictMapper.Get<Event>(); // repo is statically typed as Repository<Event>
var e = repo.Create(); // e is Event
This requires some casting inside the implementation of dict mapper, but otherwise will work fine.
If you know the type only at runtime (e.g. you have it as a Type
object), then your options are quite limited. I think the best option would be create a non-generic base type for Repository
, which works with object
s:
var repo = m_dictMapper.Get(typeof(Event)); // repo is statically typed as Repository
var e = repo.Create(); // e is object
Also, I find your Create()
method very confusing. A method with that name should return the created instance, not do something to an existing instance.
Upvotes: 1