【问题标题】:How to pass unit of work container into constructor of repository using dependency injection如何使用依赖注入将工作单元容器传递到存储库的构造函数中
【发布时间】:2011-03-20 01:31:13
【问题描述】:

我正在尝试研究如何在 ASP.NET Web 应用程序中完成存储库模式的实现。

目前,我为每个域类定义了一个存储库接口,例如加载和保存该类的实例。

每个存储库接口都由一个执行 NHibernate 的类实现。 Castle Windsor根据web.config将类的DI整理到界面中。下面提供了一个已实现类的示例:

  public class StoredWillRepository : IStoredWillRepository
  {
    public StoredWill Load(int id)
    {
      StoredWill storedWill;
      using (ISession session = NHibernateSessionFactory.OpenSession())
      {
        storedWill = session.Load<StoredWill>(id);
        NHibernateUtil.Initialize(storedWill);
      }
      return storedWill;
    }

    public void Save(StoredWill storedWill)
    {
      using (ISession session = NHibernateSessionFactory.OpenSession())
      {
        using (ITransaction transaction = session.BeginTransaction())
        {
          session.SaveOrUpdate(storedWill);
          transaction.Commit();
        }
      }
    }
  }

正如在前一个线程中所指出的,存储库类需要接受一个工作单元容器(即 ISession),而不是在每个方法中实例化它。

我预计工作单元容器将在需要时由每个 aspx 页面创建(例如,在属性中)。

然后,当 Windsor 为我创建此工作单元容器实例时,如何指定将其传递到 StoredWillRepository 的构造函数中?

或者这种模式完全错误?

再次感谢您的建议。

大卫

【问题讨论】:

    标签: asp.net nhibernate dependency-injection repository-pattern isession


    【解决方案1】:

    我有一个基于 NHibernate 构建的持久性框架,用于一些 Web 应用程序。它使用 Unity 提供的具体实例将 NH 实现隐藏在 IRepositoryIRepository&lt;T&gt; 接口后面(因此,理论上我可以很容易地将 NHibernate 换成实体框架)。

    由于 Unity 不(或至少我使用的版本不)支持传入除了依赖注入本身之外的构造函数参数,因此无法传入现有的 NH ISession;但我确实希望 UOW 中的所有对象共享相同的 ISession。

    我通过一个控制存储库类来解决这个问题,该存储库类在每个线程的基础上管理对 ISession 的访问:

        public static ISession Session
        {
            get
            {
                lock (_lockObject)
                {
                    // if a cached session exists, we'll use it
                    if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                    {
                        return (ISession)PersistenceFrameworkContext.Current.Items[NHibernateRepository.SESSION_KEY];
                    }
                    else
                    {
                        // must create a new session - note we're not caching the new session here... that's the job of
                        // BeginUnitOfWork().
                        return _factory.OpenSession(new NHibernateInterceptor());
                    }
                }
            }
        }
    

    在此示例中,PersistenceFrameworkContext.Current.Items 访问一个 IList&lt;object&gt;,如果不在 Web 上下文中,则该 ThreadStatic 或在 HttpContext.Current.Items 中(以避免线程池问题)。对属性的第一次调用从存储的工厂实例中实例化 ISession,随后的调用只是从存储中检索它。锁定会稍微减慢速度,但不如锁定 appdomain 范围的静态 ISession 实例。

    然后我有 BeginUnitOfWorkEndUnitOfWork 方法来处理 UOW - 我明确禁止嵌套 UOW,因为坦率地说,它们很难管理。

        public void BeginUnitOfWork()
        {
            lock (_lockObject)
            {
                if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                    EndUnitOfWork();
    
                ISession session = Session;
                PersistenceFrameworkContext.Current.Items.Add(SESSION_KEY, session);
            }
        }
    
        public void EndUnitOfWork()
        {
            lock (_lockObject)
            {
                if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                {
                    ISession session = (ISession)PersistenceFrameworkContext.Current.Items[SESSION_KEY];
                    PersistenceFrameworkContext.Current.Items.Remove(SESSION_KEY);
                    session.Flush();
                    session.Dispose();
                }
            }
        }
    

    最后,一对方法提供对特定于域类型的存储库的访问:

        public IRepository<T> For<T>()
            where T : PersistentObject<T>
        {
            return Container.Resolve<IRepository<T>>();
        }
    
        public TRepository For<T, TRepository>()
            where T : PersistentObject<T>
            where TRepository : IRepository<T>
        {
            return Container.Resolve<TRepository>();
        }
    

    (这里,PersistentObject&lt;T&gt; 是一个提供 ID 和 Equals 支持的基类。)

    因此,对给定存储库的访问符合模式

    NHibernateRepository.For<MyDomainType>().Save();
    

    然后将其覆盖以便您可以使用

    MyDomainType.Repository.Save();
    

    如果给定类型有一个专门的存储库(即需要的比它可以从IRepository&lt;T&gt; 获得的更多),那么我创建一个派生自IRepository&lt;T&gt; 的接口,一个继承自我的IRepository&lt;T&gt; 实现的扩展实现,并在域中类型本身我使用new覆盖静态Repository属性

        new public static IUserRepository Repository
        {
            get
            {
                return MyApplication.Repository.For<User, IUserRepository>();
            }
        }
    

    (MyApplication [在实际产品中被称为不那么时髦的东西] 是一个外观类,负责通过 Unity 提供 Repository 实例,因此您无需依赖域类中的特定 NHibernate 存储库实现.)

    这为我提供了通过 Unity 实现存储库的完全可插拔性、轻松地在代码中访问存储库而无需跳槽,以及透明的每线程 ISession 管理。

    除了上面的代码之外,还有很多代码(我已经大大简化了示例代码),但你明白了。

    MyApplication.Repository.BeginUnitOfWork();
    User user = User.Repository.FindByEmail("wibble@wobble.com");
    user.FirstName = "Joe"; // change something
    user.LastName = "Bloggs";
    // you *can* call User.Repository.Save(user), but you don't need to, because...
    MyApplication.Repository.EndUnitOfWork();
    // ...causes session flush which saves the changes automatically
    

    在我的 Web 应用程序中,我有每个请求的会话,因此 BeginUnitOfWorkEndUnitOfWork 分别在 BeginRequestEndRequest 中被调用。

    【讨论】:

    • 太棒了。我肯定会检查我的代码是否可以使用您的一些优点。 xD
    • Stack Overflow 需要一个按钮来显示“可能是正确的答案,但坦率地说我不明白”。我会在这里点击它。我会试着在整个周末消化它,但它可能是喝醉之后的第二好。至少我很欣赏你的电子邮件地址!
    • 醉酒是一个绝妙的主意,我将在今晚晚些时候复制它:-) 至于代码,所有级别的间接和元编程确实使阅读变得更加困难。我们持久性框架的真正完整实现是关于我们武器库中最复杂的代码 - 但是一旦你完成了一次,你就不需要再做一次了。它已经为自己付出了很多倍。
    【解决方案2】:

    我的结构和你的很相似,我是这样解决你的问题的:

    1) 为了在每个方法上指定我的容器,我有一个单独的类 ("SessionManager"),然后我通过静态属性调用它。通过这样做,这是一个使用我的 Save 实现的示例:

    private static ISession NHibernateSession
    {
        get { return SessionManager.Instance.GetSession(); }
    }
    
    public T Save(T entity)
    {
        using (var transaction = NHibernateSession.BeginTransaction())
        {
            ValidateEntityValues(entity);
            NHibernateSession.Save(entity);
    
            transaction.Commit();
        }
    
        return entity;
    }
    

    2) 我的容器不是在每个 ASPX 页面上创建的。我在 global.asax 页面上实例化了我所有的 NHibernate 优点。

    ** 更多的东西出现了 **

    3) 您不需要帮助器来实例化负载。您不妨使用 Get 而不是 Load。更多信息@Difference between Load and Get

    4) 使用您当前的代码,您将不得不为您需要的每个域对象(StoredWillRepository、PersonRepository、CategoryRepository 等?)重复几乎相同的代码,这似乎是一个拖累。您可以很好地使用generic class 在 NHibernate 上进行操作,例如:

    public class Dao<T> : IDao<T>
    {
        public T SaveOrUpdate(T entity)
        {
            using (var transaction = NHibernateSession.BeginTransaction())
            {
                NHibernateSession.SaveOrUpdate(entity);
                transaction.Commit();
            }
    
            return entity;
        }
    }
    

    在我的实现中,我可以使用something like:

    Service<StoredWill>.Instance.SaveOrUpdate(will);
    

    【讨论】:

    • 非常感谢您的意见。我将在下面用编号的 cmets 回复您的观点:
    • 1) 静态 ISession?除非我误解了,否则这意味着 ISession(它不是线程安全的)在应用程序的所有线程之间共享 - 非常危险。这不正确吗?
    • 2) 容器用于什么?抱歉,我没明白你的意思。
    • 3) 在我发布的代码中,我使用 NHibernate.Util 来预加载实体。注意 Session 在方法调用中被创建和销毁。这意味着返回的实体将在 Session 之外,因此当它尝试延迟加载时,会引发异常。由于我现在将 ISession 传递给工厂,因此我不必预加载实体,因为它会正常延迟加载。
    • 显然 ThreadStatic 在 ASP.NET 中不是一个好主意:hanselman.com/blog/StoringThingsOnThreads.aspx
    【解决方案3】:

    从技术上讲,我的问题的答案是使用 container.Resolve 的重载,它允许您将构造函数参数指定为匿名类型:

    IUnitOfWork unitOfWork = [Code to get unit of work];
    _storedWillRepository = container.Resolve<IStoredWillRepository>(new { unitOfWork = unitOfWork });
    

    但让我们面对现实吧,其他人提供的答案信息量要大得多。

    【讨论】:

    • 正如您所说,这在技术上是我问题的答案,即使在架构上不是最佳解决方案。我在给你打招呼。
    猜你喜欢
    • 2015-12-17
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多