如何使用依赖注入将工作单元容器传递到存储库的构造函数中

发布于 2024-09-11 10:51:12 字数 1118 浏览 8 评论 0 原文

我正在尝试弄清楚如何在 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 的构造函数中?

或者这种模式完全错误?

再次感谢您的建议。

大卫

I'm trying to work out how to complete my implementation of the Repository pattern in an ASP.NET web application.

At the moment, I have a repository interface per domain class defining methods for e.g. loading and saving instances of that class.

Each repository interface is implemented by a class which does the NHibernate stuff. Castle Windsor sorts out the DI of the class into the interface according to web.config. An example of an implemented class is provided below:

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

As pointed out in a previous thread, the repository class needs to accept an unit of work container (i.e. ISession) rather than instantiating it in every method.

I anticipate that the unit of work container will be created by each aspx page when needed (for example, in a property).

How do I then specify that this unit of work container instance is to be passed into the constructor of StoredWillRepository when Windsor is creating it for me?

Or is this pattern completely wrong?

Thanks again for your advice.

David

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

呢古 2024-09-18 10:51:12

我有一个基于 NHibernate 构建的持久性框架,在一些 Web 应用程序中使用。它将 NH 实现隐藏在 IRepositoryIRepository 接口后面,并使用 Unity 提供的具体实例(因此理论上我可以将 NHibernate 替换为 Entity)框架相当容易)。

由于 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> 如果不在 Web 上下文中,则存储在 ThreadStatic 中;如果在 Web 上下文中,则存储在 HttpContext.Current.Items 中(以避免线程池问题)。对该属性的第一次调用会从存储的工厂实例中实例化 ISession,后续调用只是从存储中检索它。锁定会稍微减慢速度,但不会像仅锁定应用程序域范围内的静态 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 是提供 ID 和 Equals 支持的基类。)

因此,对给定存储库的访问是在 然后,该模式

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

被外观化,以便您可以使用

MyDomainType.Repository.Save();

给定类型有专门存储库的情况(即需要的内容超出了从 IRepository 可以获得的内容),然后我创建一个从 IRepository,一个继承自我的 IRepository 实现的扩展实现,在域类型本身中,我使用 Repository 覆盖静态 Repository 属性code>new

    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("[email protected]");
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 应用程序中,我有每个请求的会话,因此在 BeginRequestEndRequest 中调用 BeginUnitOfWorkEndUnitOfWork > 分别。

I have a persistence framework built on top of NHibernate that is used in a few Web apps. It hides the NH implementation behind an IRepository and IRepository<T> interface, with the concrete instances provided by Unity (thus I could in theory swap out NHibernate for, say, Entity Framework fairly easily).

Since Unity doesn't (or at least the version I'm using doesn't) support the passing in of constructor parameters other than those that are dependency injections themselves, passing in an extant NH ISession isn't possible; but I do want all objects in the UOW to share the same ISession.

I solve this by having a controlling repository class that manages access to the ISession on a per-thread basis:

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

In this example, PersistenceFrameworkContext.Current.Items accesses an IList<object> that is stored either ThreadStatic if not in a Web context, or within HttpContext.Current.Items if it is in a Web context (to avoid thread-pool problems). The first call to the property instantiates the ISession from the stored factory instance, subsequent calls just retrieve it from storage. The locking will slow things down slightly but not as much as just locking an appdomain-scoped static ISession instance.

I then have BeginUnitOfWork and EndUnitOfWork methods to take care of the UOW - I have specifically disallowed nested UOWs because frankly they were a pain to manage.

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

Finally, a pair of methods provide access to the domain-type-specific repositories:

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

(Here, PersistentObject<T> is a base class providing ID and Equals support.)

Access to a given repository is thus in the pattern

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

This is then facaded over such that you can use

MyDomainType.Repository.Save();

Where a given type has a specialised repository (ie needs more than it can get from IRepository<T>) then I create an interface deriving from IRepository<T>, an extending implementation inheriting from my IRepository<T> implementation, and in the domain type itself I override the static Repository property using new

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

(MyApplication [which is called something less noddy in the real product] is a facade class which takes care of supplying the Repository instance via Unity so you have no dependency on the specific NHibernate repository implementation within your domain classes.)

This gives me full pluggability via Unity for the repository implementation, easy access to the repository in code without jumping through hoops, and transparent, per-thread ISession management.

There's lots more code than just what's above (and I've simplified the example code a great deal), but you get the general idea.

MyApplication.Repository.BeginUnitOfWork();
User user = User.Repository.FindByEmail("[email protected]");
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

In my Web app, I have session-per-request, so BeginUnitOfWork and EndUnitOfWork get called in BeginRequest and EndRequest respectively.

玩套路吗 2024-09-18 10:51:12

我有一个与你的非常相似的结构,这是我解决你的问题的方法:

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) 你不需要有一个助手来实例化 Load。您不妨使用 Get 而不是 Load。更多信息@ Load 和 Get 之间的区别

4)使用当前的代码,您必须为您需要的每个域对象(StoredWillRepository、PersonRepository、CategoryRepository 等?)重复几乎相同的代码,这看起来很麻烦。您很好地使用通用类 通过 NHibernate 进行操作,例如:

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

        return entity;
    }
}

在我的实现中,我可以使用 类似于

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

I have a pretty similar structure to yours, and here's how I solve your question:

1) To specify my container on each method, I have a separate class ("SessionManager") which I then invoke via a static property. By doing so, here's an example using my Save implementation:

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) My container is not created on each ASPX page. I instantiate all of my NHibernate goodness on the global.asax page.

** A few more things spring up **

3) You don't need to have a helper to instantiate the Load. You might as well use Get instead of Load. More information @ Difference between Load and Get.

4) Using your current code, you would have to repeat pretty much the same code for each domain object you need (StoredWillRepository, PersonRepository, CategoryRepository, etc..?), which seems like a drag. You could very well use a generic class to operate over NHibernate, like:

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

        return entity;
    }
}

In my implementation, I could then use something like:

Service<StoredWill>.Instance.SaveOrUpdate(will);
魔法唧唧 2024-09-18 10:51:12

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

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

但是让我们面对现实吧,其他人提供的答案信息丰富得多。

Technically, the answer to my question is to use the overload of container.Resolve which allows you to specify the constructor argument as an anonymous type:

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

But let's face it, the answers provided by everyone else have been much more informative.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文