Code Copied

通过注册Repository和Service看Unity的生命周期管理

1. 项目概要说明

首先咱的项目采用的是当前一种烂大街的架构,借鉴了一些ddmvc4中的一些底层设计,项目简易的架构层次和依赖关系如下。

image

本篇不是介绍项目架构相关的知识,所以这里只对各层进行简单说明。

  • Domain Model层:定义领域对象和对象关系
  • Core层:定义各个层级的抽象接口,包含IUnitOfWork, IRepository, IService接口等
  • DAL层:封装Data Context(Entity Framework)和Unit Of Work(Transaction)的实现
  • Repository层:定义各个领域对象对应的Repository接口和实现
  • Service层:定义各个领域对象的用例接口和实现,可以看作较粗粒度的业务处理
  • Web层:使用ASP.NET MVC和Bootstrap框架实现前端展现,Repository和Service通过Unity在这一层注册

2. Unity内置的6种生命周期

2.1 TraientLifetimeManager

瞬态生命周期,默认情况下,在使用RegisterType进行对象关系注册时如果没有指定生命周期管理器则默认使用这个生命周期管理器,这个生命周期管理器就如同其名字一样,当使用这种管理器的时候,每次通过Resolve或ResolveAll调用对象的时候都会重新创建一个新的对象。

2.2 ContainerControlledLifetimeManager

容器控制生命周期管理,这个生命周期管理器是RegisterInstance默认使用的生命周期管理器,也就是单件实例,UnityContainer会维护一个对象实例的强引用,每次调用的时候都会返回同一对象

2.3 HirearchicalLifetimeManager

分层生命周期管理器,这个管理器类似于ContainerControlledLifetimeManager,也是由UnityContainer来管理,也就是单件实例。不过与ContainerControlledLifetimeManager不 同的是,这个生命周期管理器是分层的,因为Unity的容器时可以嵌套的,所以这个生命周期管理器就是针对这种情况,当使用了这种生命周期管理器,父容器 和子容器所维护的对象的生命周期是由各自的容器来管理

2.4 PerResolveLifetimeManager

这个生命周期管理器就像TransientLifetimeManager,但是针对于循环引用的对象的场景,它能够重用已经创建的对象,而不会因为循环引用重复创建对象。

下面的示例使用了PerResolveLifetimeManager:

public interface IPresenter 
{ }

public class MockPresenter : IPresenter
{
    public IView View { get; set; }

    public MockPresenter(IView view)
    {
        View = view;
    }
}

public interface IView
{
    IPresenter Presenter { get; set; }
}

public class View : IView
{
    [Dependency]
    public IPresenter Presenter { get; set; }
}

注册时使用PerResolveLifetimeManager

public void ViewIsReusedAcrossGraph()
{
    var container = new UnityContainer()
        .RegisterType<IPresenter, MockPresenter>()
        .RegisterType<IView, View>(new PerResolveLifetimeManager());
    var view = container.Resolve<IView>();
    var realPresenter = (MockPresenter) view.Presenter;
}

从这个例子中可以看出,有2个接口IPresenter和IView,还有2个类MockPresenter和View分别实现这2个接口,同时这2个类 中都包含了对另外一个类的对象属性,这个就是一个循环引用。
而对应的这个生命周期管理就是针对这种情况而新增的,其类似于 TransientLifetimeManager,但是其不同在于,如果应用了这种生命周期管理器,则在第一调用的时候会创建一个新的对象,而再次通过 循环引用访问到的时候就会返回先前创建的对象实例(单件实例)。

2.5 PerThreadLifetimeManager

在解析时为每一个线程创建一个目标对象的实例,但是每个实例在线程内部都是单件实例(即在线程内共享一个实例)。

简单的应用场景:多线程文件传输时(文件可能属于不同的应用程序),每种文件的配置信息(源文件地址,传输目标地址,名称等)在一个线程内只实例化一次。

2.6 ExternallyControlledLifetimeManager

外部控制生命周期管理器,这个生命周期管理允许你使用RegisterType和RegisterInstance来注册对象之间的关系,但是其只会对对象保留一个弱引用,其生命周期交由外部控制,也就是意味着你可以将这个对象缓存或者销毁而不用在意UnityContainer,而当其他地方没有强引用这个对象时,其会被GC给销毁掉。

上述6种生命周期的使用方式理解起来很容易,具体的例子我就不再一一列举了,诸位也可以参考MSDN上的一些说明和示例代码

http://msdn.microsoft.com/zh-cn/library/ff660872(v=pandp.20).aspx

http://msdn.microsoft.com/en-us/library/dn178463(v=pandp.30).aspx#sec37

3. 使用Unity注册Repository层和Service层

3.1 使用ContainerControlled管理生命周期

在UnityConfig.cs的public static void RegisterTypes(IUnityContainer container)方法中替换如下注册代码

// 注册IUnitOfWork
container.RegisterType<IQueryableUnitOfWork, UnitOfWork>();
// 注册泛型IRepository<T>
container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
// 注册泛型IService<T>
container.RegisterType(typeof(IService<>), typeof(Service<>));

// 注册领域对象的数据访问接口IDomainRepository
container.RegisterTypes(
    AllClasses.FromLoadedAssemblies().Where(n => n.Namespace == "BMS.Repository"),
    WithMappings.FromMatchingInterface,
    WithName.Default,
    WithLifetime.ContainerControlled);

// 注册领域对象的业务访问接口IDomainService
container.RegisterTypes(
    AllClasses.FromLoadedAssemblies().Where(n => n.Namespace == "BMS.Service"),
    WithMappings.FromMatchingInterface,
    WithName.Default,
    WithLifetime.ContainerControlled);
  • IUnitOfWork, IRepository, IService注册时使用了TransientLifetimeManger生命周期管理器
  • Repository和Service在注册时采用了ContainerControlleredLifetimeManger生命周期管理器

但是上述针对Repository和Service时会产生一个问题,下面演示了问题产生的过程。

① 初始化时,从DB读取了4条数据,数据实体都是代理对象(Entity Framework使用Dynamic Proxy模式进行对象的访问和状态更新)

image

② 页面初始化时,显示了4条数据

image

③新建一条Dept数据后,画面确实显示了5条数据

image

④实际的数据对象(再次刷新页面)

image

现在问题点出来了,新追加的Dept对象在读取时并非动态代理对象,而是一个元对象。二非代理对象的状态是无法被其他关联对象所获取的。
与此同时,在这个部门添加一个职员信息(在Dept对象中添加一个Employee对象)后,并不能得到我所预期的效果,请看下列图示:

⑤职员信息画面添加职员信息前

image

⑥添加职员信息

image

⑦保存职员信息,再次回到职员信息列表

image

由于我使用了ContainerControlled的管理Repository和Service对象的生命周期,导致所有的Repository和Service对象都是单例的。
Repository对象是单例的,封装在Repository对象中的DbContext对象也相当于是单例的。
虽然DbContext的SaveChanges方法将数据写入到DB里面了,但是再次获取对象List时(相当于另外一个Request),仍然使用的是未刷新的DbContext对象中的DbSet<T>。

解决方法:将注册Repository和Service的Lifetime.ContainerControlled去掉,去掉后将使用默认的TransientLifetimeManager

// 注册领域对象的数据访问接口IDomainRepository
container.RegisterTypes(
    AllClasses.FromLoadedAssemblies().Where(n => n.Namespace == "BMS.Repository"),
    WithMappings.FromMatchingInterface,
    WithName.Default);

// 注册领域对象的业务访问接口IDomainService
container.RegisterTypes(
    AllClasses.FromLoadedAssemblies().Where(n => n.Namespace == "BMS.Service"),
    WithMappings.FromMatchingInterface,
    WithName.Default);

3.2 使用PerRequestLifetimeManagement管理UnitOfWork对象生命周期

在IDeptRepository的具体实现中,其依赖了IUnitOfWork接口:

public interface IDeptRepository : IRepository<Dept>
{
}

public class DeptRepository : Repository<Dept>, IDeptRepository
{
    public DeptRepository(IQueryableUnitOfWork unitOfWork) : base(unitOfWork)
    {
    }
}

在IEmployeeService的具体实现中,又依赖了IDeptRepository, IPositionRepository和IEmployeeRepository接口。

public interface IEmployeeService : IService<Employee>
{
    IEnumerable<Dept> Depts { get; }
    IEnumerable<Position> Positions { get; }
}

public class EmployeeService : Service<Employee>, IEmployeeService
{
    public EmployeeService(IEmployeeRepository repository) : base(repository)
    {
    }

    [Dependency]
    public IDeptRepository DeptRepository { get; set; }
    [Dependency]
    public IPositionRepository PositionRepository { get; set; }

    public IEnumerable<Dept> Depts
    {
        get
        {
            return DeptRepository.FindAll();
        }
        
    }

    public IEnumerable<Position> Positions
    {
        get
        {
            return PositionRepository.FindAll();
        }
    }
}

UnitOfWork对象继承自Entity Framework的DbContext对象,里面定义了各个领域对象的DbSet<T>属性。
由于UnitOfWork的代码较多,这里我就不贴出来了。
请仔细观察IEmployeeService的实现,其依赖于3个Repository接口,由于每个IDomainRepository接口都是被Unity的TransientLifetimeManager管理的。
所以这将会造成在一次请求中实例化IEmployee的对象时,会实例化UnitOfWork对象3次(相当于实例化DbContext对象3次)。
这是另一个问题了——一次请求中我们只需要获取一个DbContext对象就足以。

在Web Application开发中,往往我们会在每一次请求过程中,获取一个DbContext对象(不考虑缓存情况),恰好Unity提供了PerRequestLifetimeManager来帮我们实现这一需求。

在UnityConfig中完整的注册代码:

public static void RegisterTypes(IUnityContainer container)
{
    // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
    // container.LoadConfiguration();

    // TODO: Register your types here
    // container.RegisterType<IProductRepository, ProductRepository>();

    // 注册IUnitOfWork,使用PreRequestLifetimeManager每次请求时获取最新的DbContext对象
    container.RegisterType<IQueryableUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());
    // 注册泛型IRepository<T>
    container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
    // 注册泛型IService<T>
    container.RegisterType(typeof(IService<>), typeof(Service<>));

    // 注册领域对象的数据访问接口IDomainRepository
    container.RegisterTypes(
        AllClasses.FromLoadedAssemblies().Where(n => n.Namespace == "BMS.Repository"),
        WithMappings.FromMatchingInterface,
        WithName.Default);

    // 注册领域对象的业务访问接口IDomainService
    container.RegisterTypes(
        AllClasses.FromLoadedAssemblies().Where(n => n.Namespace == "BMS.Service"),
        WithMappings.FromMatchingInterface,
        WithName.Default);
}

4. 总结

  • 本文简单介绍了Unity的6种生命周期管理器
  • 在注册Repsository和Service的接口和对应实现时应使用TransientLifetimeManager管理生命周期
  • 在注册UnitOfwork的接口和对应实现时应使用PerRequestLifetimeManager管理生命周期