Multiple instance for service which should resolve to the same type

Dec 3, 2013 at 6:06 PM
Edited Dec 3, 2013 at 6:07 PM
Hello,

I found behavior in SimpleInjector which strikes me as very odd. Please observe the following code:
        container.Register<ApplicationDbContext>(() => new ApplicationDbContext(), Lifestyle.Singleton);
        container.Register<IApplicationDbContext, ApplicationDbContext>();
        container.Register<DbContext>(() => (DbContext) container.GetInstance<IApplicationDbContext>());

        bool isSame1 = Object.ReferenceEquals(container.GetInstance<IApplicationDbContext>(), container.GetInstance<ApplicationDbContext>());
        bool isSame2 = Object.ReferenceEquals(container.GetInstance<IApplicationDbContext>(), container.GetInstance<DbContext>());
        bool isSame3 = Object.ReferenceEquals(container.GetInstance<ApplicationDbContext>(), container.GetInstance<DbContext>());
        bool isSame4 = Object.ReferenceEquals(container.GetInstance<ApplicationDbContext>(), container.GetInstance<ApplicationDbContext>());
        bool isSame5 = Object.ReferenceEquals(container.GetInstance<IApplicationDbContext>(), container.GetInstance<IApplicationDbContext>());
The above code causes 7 constructor calls to ApplicationDbContext. I'd expect it to be just one, since I'd expected every requested service to resolve to ApplicationDbContext, including IApplicationDbContext and DbContext.

The result of above code is that isSame4 is true and all others resolve to false.

Is this intended behaviour? I cannot recall Ninject doing something like this.

From my point of view correct behaviour would be:
container.GetInstance<IApplicationDbContext>() == container.GetInstance<ApplicationDbContext>() == container.GetInstance<DbContext>()
Coordinator
Dec 3, 2013 at 6:34 PM
Edited Dec 3, 2013 at 6:38 PM
Yes, this is intended behavior. When calling container.Register<IApplicationDbContext, ApplicationDbContext>, you tell the container that an ApplicationDbContext should be created when an IApplicationDbContext abstraction is requested. This registration works in isolation, and won't call back into the container to see whether there is a registration for ApplicationDbContext as service type. We did consider this scenario, but noticed that the interrelationship between the registrations could itself lead to unexpected behavior or extra completity, so we explicitly decided not to do this.

There are multiple ways to solve this, for instance like this:
container.RegisterSingle<ApplicationDbContext>();
container.RegisterSingle<IApplicationDbContext, ApplicationDbContext>();
container.RegisterSingle<DbContext, ApplicationDbContext>();
Downside of this registration is that the application will contain three ApplicationDbContext; this might not be what you desire. Another option is to have one core-registration and let the other two registrations call back into the container:
container.RegisterSingle<ApplicationDbContext>();
container.Register<IApplicationDbContext>(() => container.GetInstance<ApplicationDbContext>()); 
container.Register<DbContext>(() => container.GetInstance<ApplicationDbContext>());
Another option is to create one singleton Registration and add that registration for each service type that needs to be resolved:
var registration = Lifestyle.Singleton.CreateRegistration<ApplicationDbContext>(
    () => new ApplicationDbContext(), container);

container.AddRegistration(typeof(ApplicationDbContext), registration);
container.AddRegistration(typeof(IApplicationDbContext), registration);
container.AddRegistration(typeof(DbContext), registration);
This last option is probably the best option, since it integrates nicely with the Diagnostic Services.

btw. Note that it's quite unusual to register a DbContext as singleton. It's for more usual to have one DbContext per 'scope'.

I hope this helps.
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:15 PM
Dec 3, 2013 at 6:47 PM
Thanks for your quick response.

The singleton was just to illustrate my example and to prevent confusion about the lifestyle.

About diagnostics services and verification: The Verify method causes all instances in the container to be resolved, right? Does the method also make sure the IDisposable instances are disposed of? Is resolving each instance not unneccessary, since by checking the registered types in the container it would be able to check whether alle registered services can be created without doing actual object creation right?
Coordinator
Dec 3, 2013 at 7:40 PM
> The Verify method causes all instances in the container to be resolved, right?

Correct. It might even instantiate a registered object multiple times (although that is subject to change).

> Does the method also make sure the IDisposable instances are disposed of?

No. In fact the container itself never disposes instances. Only instances registered with a scoped lifestyle (such as WebRequest, WcfOperation, and LifetimeScope) that are created inside the context of such scope are usually disposed. Instances created during Verify() will usually not be disposed, since there's usually no scope active at that point. Here are two related discussions about this you might find interesting:
> Is resolving each instance not unneccessary,

Instead of doing a 'runtime' check, the container could in theory just do statical analysis by inspecting the dependency graph. Problem with this is that many configurations contain registrations that can't be statically statically. A simple example is when registering a Func<T> delegate that calls back into the container. For instance:
container.Register<DbContext>(() => container.GetInstance<ApplicationDbContext>());
The Verify() method is just a 'brute force' way to check every registered instance. Creating each instance just gives you the most certainty abut the correctness of your configuration. This does mean however, that it is not always suitable to call Verify().