I have one project functioning perfectly using Unity. I try switching to use Simple Injector instead and now NO changes ever get saved in my database. I believe it has to do with the lifetime of the registered components. Here is the Unity container registration:
private IUnityContainer GetUnityContainer()
{
IUnityContainer container = new UnityContainer()
.RegisterType<IDatabaseFactory, DatabaseFactory>(
new HttpContextLifetimeManager<IDatabaseFactory>())
.RegisterType<IUnitOfWork, UnitOfWork>(
new HttpContextLifetimeManager<IUnitOfWork>())
.RegisterType<ICategoryRepository, CategoryRepository>(
new HttpContextLifetimeManager<ICategoryRepository>())
.RegisterType<ICategoryService, CategoryService>(
new HttpContextLifetimeManager<ICategoryService>());
return container;
}
And here is the new Simple Injector registration.
container.Register<IDatabaseFactory, DatabaseFactory>();
container.Register<IUnitOfWork, UnitOfWork>();
container.Register<ICategoryRepository, CategoryRepository>();
container.Register<ICategoryService, CategoryService>();
I'm not sure how the HttpContextLifetimeManager
comes into play with Simple Injector. MVC is the client for the unity example, but I'm changing to a WPF project and Simple Injector. Any suggestions are much appreciated. Thanks.
@Steven. Thanks for your comment. I just discovered that since my RepositoryBase and my UnitOfWork inject an IDatabaseFactory in their constructors that I needed to use container.RegisterSingle<IDatabaseFactory, DatabaseFactory>()
. This resolved one issue. I still have a problem with lifetime though. Since my consuming app is WPF, how will the RegisterPerWebRequest work?
My project has a DataLayer >> BusinessLayer >> WcfService >> WPF Front end. Simple Injector is set on the WcfService project and the business layer has Boostrapper to register items there. As of now, my WPF client will GetAllCountries() and display in a grid. If I change the name of one and try to update, I get the "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key." error. I've done some debugging and find that after the GetCountries service call in the WPF client, when I go back to try to update, I see ALL of the countries are attached to the context via dbContext.ChangeTracker.Entries(). At this point I should have NO entities being tracked as my context should have been disposed after the first unit of work.
In an MVC app the RegisterPerWebRequest fixes that, but what is the equivalent for WPF? I'm going to install the extension now and try it anyway but I have a feeling it isn't the solution I'm looking for.. or is it? Thanks again for the help.
OK. I did a bit more digging and found a solution that works. I'm just not sure if it's the correct one. Anyway, now in my BLL where there is a bootstrapper to register things, I can register like this:
container.RegisterPerWcfOperation<IDatabaseFactory, DatabaseFactory>();
container.RegisterPerWcfOperation<IUnitOfWork, UnitOfWork>();
container.RegisterPerWcfOperation<ICountryRepository, CountryRepository>();
That gives me what I was looking for. Only a single instance of DatabaseFactory is ever created and thus my repository and unit of work share it like they should. Also, after GetCountries() on the client, when I do my second call to the service to perform and update, I check the dbContext.ChangeTracker.Entries() and see that there are NO entities being tracked, which is correct. I can now attach, set to modify, and call SaveChanges without getting the duplicate key error. Does this seem ok? Thanks.
The Register
overloads over Simple Injector register types using the transient lifestyle (which means no caching). Every time you request an instance (or inject an instance) a new instance is created. In Unity this is the same; the default lifestyle is transient.
It seems that registering those types with a Per Web Request lifestyle is quite essential. It's not strange that changes are not committed to the database when the class that does those commits on an IUnitOfWork
gets a different instance than the class who actually makes the changes to the IUnitOfWork
.
Simple Injector's equivalent to Unity's HttpContextLifetimeManager
is the WebRequestLifestyle. This lifestyle is not part of the core library, but is available as NuGet package.
After you included this in your project, you can do the following registration:
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
container.Register<IDatabaseFactory, DatabaseFactory>(Lifestyle.Scoped);
container.Register<IUnitOfWork, UnitOfWork>(Lifestyle.Scoped);
container.Register<ICategoryRepository, CategoryRepository>(Lifestyle.Scoped);
container.Register<ICategoryService, CategoryService>(Lifestyle.Scoped);
Or the equivalent:
Lifestyle lifestyle = new WebRequestLifestyle();
container.Register<IDatabaseFactory, DatabaseFactory>(lifestyle);
container.Register<IUnitOfWork, UnitOfWork>(lifestyle);
container.Register<ICategoryRepository, CategoryRepository>(lifestyle);
container.Register<ICategoryService, CategoryService>(lifestyle);
The default behavior of the WebRequestLifestyle
is to dispose created instances when the web request ends. No special registration for this is required (Simple Injector hooks up an HttpModule
for you when the application starts).
My apologies for not reading your question to the last line. I missed the fact that you want to configure it using a WPF client.
As you probably know, since your client is a different application than the WCF service is, you'll have two Composition Roots in your system; one for the client, one for the service. Their registration will probably be quite different. Starting with Simple Injector v4.1, for a WCF service, you would indeed need a AsyncScopedLifestyle or when you follow the reference architecture at dotnetjunkie/solidservices, you'll find it as easy to use ThreadScopedLifestyle and define a scope explicitly in your two Execute
methods.
I find managing the lifetime of objects in clients of two tier applications (client -> database) rather hard, since it is hard to define a unit of work for a certain scope. Since you are using the command/handler + query/handler approach, things will get so much easier. There won't be any unit of work on the client. Just on the server. Your presenter can just depend on several IQueryHandler<TQuery, TResult>
and ICommandHandler<TCommand>
interfaces and you're done. A query doesn't change state and a command should be an atomic operation. In other words, a unit of work is only needed within the boundary of an executing command.