I am trying to migrate from Unity to Simple Injector in my new project. It is so much faster than Unity that I have to give it a shot. I have had a few bumps, but nothing I could not work around. But I have hit another one with "Look-up by Key"

I have read this where the creater of simple injector states his belief that you should not need to resolve more than one class per interface.

I must be a poor programmer, because I have done that with Unity (which supports it very well) and want to do it in my current project.

My scenario is that I have an IRepository interface. I have two separate repositories I want to abstract using the IRepository interface. Like this:

container.Register<FirstData>(() => new FirstData());
container.Register<IRepository>(
           () => new GenericRepository(container.GetInstance<FirstData>()));


container.Register<SecondEntities>(() => new SecondEntities());
container.Register<IRepository>(
           () => new GenericRepository(container.GetInstance<SecondData>()));

IRepository/GenericRepository is a fairly common abstraction, but you can only have one in SimpleInjector

In Unity I could register both of my repositories and then setup my constructor injection to inject the instance that I needed. This was accomplished using a key for the instance. (I did not need to do a Resolve call in my normal code nor add a dependency to Unity outside my setup.)

With simple injector this does not work. But, for better or worse, the owner of Simple Injector thinks this feature is a bad idea.

NOTE: The author's "in app" system looks like it uses a string key for look-up, but it still requires a different class each time (DefaultRequestHandler, OrdersRequestHandler and CustomersRequestHandler). I just have a GenericRepostory which allows me to abstract my repository methods regardless of what I am connected to.

I suppose I could inherit my GenericRepostory for each time I want to instantiate it. Or have it take a random type parameter that I don't need. But that muddles my design, so I am hoping for another way to do it.

So are there any work arounds that don't have me creating bogus types to differentiate between my two IRepository/GenericRepository instances?

有帮助吗?

解决方案

We ended up changing our Generic Repository to look like this:

/// The Type parameter has no funcionality within the repository, 
/// it is only there to help us differentiate when registering 
/// and resolving different repositories with Simple Injector.
public class GenericRepository<TDummyTypeForSimpleInjector> : IRepository

(We added a type parameter to it).
We then created two dummy classes like this (I changed the names of the classes to match my example):

// These are just dummy classes that are used to help 
// register and resolve GenericRepositories with Simple Injector.
public class FirstDataSelector { }
public class SecondDataSelector { }

Then I can register them like this:

container.Register<FirstData>(() => new FirstData());
container.Register(() => new GenericRepository<FirstDataSelector>
                   (container.GetInstance<FirstData>()));


container.Register<SecondEntities>(() => new SecondEntities());
container.Register(() => new GenericRepository<SecondDataSelector>
                   (container.GetInstance<SecondData>()));

(Note the generic type param on the GenericRepository and that I do not register it as an IRepository. Those two changes are essential to making this work.)

This works fine. And I am then able to use that registration in the constructor injection of my business logic.

container.Register<IFirstBusiness>(() => new FirstBusiness
               (container.GetInstance<GenericRepository<FirstDataSelector>>()));
container.Register<ISecondBusiness>(() => new SecondBusiness
               (container.GetInstance<GenericRepository<SecondDataSelector>>()));

Since my Business classes take an IRepository it works fine and does not expose the IOC container or the implementation of my repository to the business classes.

I am basically using the Type parameter as Key for lookup. (A hack I know, but I have limited choices.)

It is kind of disappointing to have to add dummy classes to my design, but our team decided that the drawback was worth it rather than abandoning Simple Injector and going back to Unity.

其他提示

Your own answer is actually quite good, but its unfortunate that you see the generic type parameter as a dummy; you should make it first class citizen of your design:

public interface IRepository<TData> { }

public clss GenericRepository<TData> : IRepository<TData>
{ 
    public GenericRepository(TData data) { }
}

This way you can simply register them as follows:

container.Register<IRepository<FirstData>, GenericRepository<FirstData>>();
container.Register<IRepository<SecondData>, GenericRepository<SecondData>>();

Your business classes can in that case simply depend on the generic IRepository<FirstData> and IRepository<SecondData> and can simply be registered as follows:

container.Register<IFirstBusiness, FirstBusiness>();
container.Register<ISecondBusiness, SecondBusiness>();

Note how the registrations given here don't use any lambdas. Simple Injector can find this out for you. This makes your DI configuration much simpler, more readable, and especially: more maintainable.

This way you make your design very explicit and unambiguous. Your design was ambiguous because you had a single (non-generic) IRepository interface that should be mapped to several implementations. Although this doesn't have to be bad in all cases, in most cases this ambiguity can and should be prevented, because this complicates your code and your configuration.

Further more, since your generic GenericRepository<T> now maps to the generic IRepository<T> we can replace all Register<IRepository<T>, GenericRepository<T>>() registrations with a single line:

// using SimpleInjector.Extensions;
container.RegisterOpenGeneric(typeof(IRepository<>),
    typeof(GenericRepository<>);

To take it even one step further, your business classes could perhaps as well benefit from generic typing. Take a look at this article for instance where each business operation gets its own class but all business operations are hidden behind the same generic ICommandHandler<TCommand> abstraction. When you do this, all business classes can be registered with a single call:

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
    typeof(ICommandHandler<>).Assembly);

This call searches the supplied assembly for implementations of the ICommandHandler<TCommand> interface and registers each found implementation in the container. You can add new pieces of business logic (use cases) without having to change the configuration. But this is just one of the many advantages of having such abstraction over your business logic. An other great advantage is that it makes adding cross-cutting concerns (such as logging, transaction handling, security, audit trail, caching, you name it) much easier to implement.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top