4

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.

4

1 回答 1

6

RegisterSimple Injector 上的重载使用瞬态生活方式注册类型(这意味着没有缓存)。每次您请求实例(或注入实例)时,都会创建一个新实例。在 Unity 中也是如此;默认的生活方式是短暂的。

似乎使用Per Web Request生活方式注册这些类型是非常必要的。当在 上进行这些提交IUnitOfWork的类与实际对IUnitOfWork.

与 Unity 等效的 Simple InjectorHttpContextLifetimeManagerWebRequestLifestyle。这种生活方式不是核心库的一部分,但可以作为 NuGet 包使用。

将其包含在项目中后,您可以进行以下注册:

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);

或等价物:

Lifestyle lifestyle = new WebRequestLifestyle();

container.Register<IDatabaseFactory, DatabaseFactory>(lifestyle);
container.Register<IUnitOfWork, UnitOfWork>(lifestyle);
container.Register<ICategoryRepository, CategoryRepository>(lifestyle);
container.Register<ICategoryService, CategoryService>(lifestyle);

的默认行为WebRequestLifestyle是在 Web 请求结束时释放创建的实例。不需要为此进行特殊注册(Simple InjectorHttpModule在应用程序启动时为您连接)。


更新:

我很抱歉没有把你的问题读到最后一行。我错过了您想使用 WPF 客户端对其进行配置的事实。

您可能知道,由于您的客户端与 WCF 服务是不同的应用程序,因此您的系统中有两个组合根;一份给客户,一份给服务。他们的注册可能会完全不同。从 Simple Injector v4.1 开始,对于WCF 服务,您确实需要AsyncScopedLifestyle,或者当您遵循dotnetjunkie/solidservices上的参考架构时,您会发现使用ThreadScopedLifestyleExecute并在两种方法中显式定义范围很容易.

我发现管理两层应用程序(客户端 -> 数据库)的客户端中对象的生命周期相当困难,因为很难为某个范围定义一个工作单元。由于您使用的是命令/处理程序 + 查询/处理程序方法,因此事情会变得容易得多。客户端上不会有任何工作单元。就在服务器上。你的演示者可以只依赖几个IQueryHandler<TQuery, TResult>ICommandHandler<TCommand>接口,你就完成了。查询不会改变状态,命令应该是原子操作。换句话说,一个工作单元只需要在执行命令的边界内。

于 2013-01-31T18:29:44.797 回答