【问题标题】:NHibernate is loading same object multiple times - please help!NHibernate 多次加载同一个对象 - 请帮助!
【发布时间】:2011-01-07 15:08:22
【问题描述】:

我刚刚阅读了我的一个 ASP.NET 页面的跟踪信息,我注意到每次需要用户时都会从数据库中加载页面用户。由于每个ISession 都应该缓存对象,我对此感到非常困惑。

从逻辑上讲,问题肯定是以下两件事之一:

  1. ISession 的缓存无法正常工作
  2. 每次请求用户时,都会使用不同的ISession 加载它

我认为问题是 2)。我正在使用 Castle Windsor 来管理对象生命周期,因此我发布了一些我正在使用的代码,以防有人可以帮助发现问题。 Castle Windsor 管理的课程有:

  1. MooseUserRepository - 用于管理 MooseUser 实例(即本例中的页面用户)的存储库类
  2. KctcUnitOfWork - ISession 的包装器

MooseUserRepositoryKctcUnitOfWork 有一个构造函数依赖,如下所示:

public MooseUserRepository(IUnitOfWork unitOfWork)
    {

    }

配置文件如下所示:

<component id="KctcUnitOfWork" service="Kctc.BusinessLayer.Kctc.IUnitOfWork,Kctc.BusinessLayer" type="Kctc.NHibernate.Kctc.UnitOfWork,Kctc.NHibernate" lifestyle="PerWebRequest"/>
<component id="MooseUserRepository" service="Kctc.BusinessLayer.Kctc.Repositories.IMooseUserRepository,Kctc.BusinessLayer" type="Kctc.NHibernate.Kctc.Repositories.MooseUserRepository,Kctc.NHibernate" lifestyle="PerWebRequest"/>

注意PerWebRequest 的生活方式。

Castle Windsor 容器只是一种名为Moose.Application 的实用程序类的静态属性,因此它始终存在:

private static IWindsorContainer _windsorContainer;

    public static IWindsorContainer WindsorContainer
    {
      get
      {
        if (_windsorContainer == null)
        {
          _windsorContainer = new WindsorContainer(new XmlInterpreter(HttpContext.Current.Server.MapPath("~/CastleWindsorConfiguration.xml")));
        }
        return _windsorContainer;
      }
    }

页面本身有一个 IMooseUserRepository 实例,如下所示:

private IMooseUserRepository _mooseUserRepository;
private IMooseUserRepository MooseUserRepository
  {
    get
    {
      if (_mooseUserRepository == null)
      {
        _mooseUserRepository = Moose.Application.WindsorContainer.Resolve<IMooseUserRepository>();
      }
      return _mooseUserRepository;
    }
  }

页面的用户通过如下所示的属性访问:

private MooseUser PageUser
  {
    get { return MooseUserRepository.Load(ApplicationSettings.UsernameFromWeb); }}

似乎这些对PageUser 的后续调用导致了重复的 SQL 命令:

txtSubject.Enabled = PageUser.CanHandleLegalWorks;
    ddlDue.Enabled = PageUser.CanHandleLegalWorks;

现在显然我可以通过将加载的 MooseUser 对象存储在私有变量中来解决这个问题,但我的理解是 ISession 应该为我做这件事。

任何人都可以冒险猜测发生了什么问题吗?

【问题讨论】:

  • 你是否有任何改变从不同的实体继承你的类?发布您的映射和实体类。
  • 根据您的应用程序中的并发性,您是否应该接听有关缓存的电话。
  • 启动 NhProf 并查看您是否为每个请求创建了多个会话 - 它有 30 天的免费试用期。
  • 大卫,你能贴出 Kctc.NHibernate.Kctc.UnitOfWork 的样子吗?

标签: nhibernate castle-windsor isession first-level-cache


【解决方案1】:

您声明如下:

从逻辑上讲,问题肯定是 以下两件事之一:

  1. ISession 的缓存工作不正常
  2. 每次请求用户时,都会使用
    不同的ISession

我认为您可能会将 Nhibernate 的第一(会话)级缓存与第二级缓存混淆。

会话的制造和丢弃都很便宜。在 Web 应用程序中,您通常会为每个请求使用一个会话。一旦你第一次获取或加载一个实体,它就会被放入一级缓存中,该缓存的范围是会话的生命周期。当 Session 在请求结束时关闭并处置时,您将无法再访问会话级缓存中的对象。事实上,每个用户都被不同的会话加载——这是完全正常的。

二级缓存的范围是会话工厂的生命周期。如果启用了二级缓存,一旦您通过其主键加载实体,它就会存储在二级缓存中,并且可以由所有会话访问而无需再次访问数据库,直到它从缓存中删除。您将需要在每个实体的基础上显式启用缓存。这是您正在寻找的行为。

进一步阅读:

编辑

您需要从NHContrib project. 中选择一个缓存提供程序您可能想要使用 Asp.Net 缓存的 SysCache2,但如果您愿意,您可以使用 MemCached 或 Velocity 或其他几个。 我还建议您尝试一下Nhibernate Profiler。我发现它在深入了解 Nhibernate 的最新进展方面非常有用。

【讨论】:

  • 我肯定在考虑一级缓存 - 每个 ISession(Web 请求)的缓存存储。我从来没有考虑过设置二级缓存,但我想它对于在请求之间保持用户数据很有用。 (不过我有点担心数据会过时。)谢谢你的想法!
  • @David:好的。您在同一网络请求中使用不同会话时遇到问题?
【解决方案2】:

就像您在问题中注意到的那样,您正在为存储库使用 PerWebRequest 生活方式,因此将在每个请求上重新创建 ISession(由存储库使用)。 我发现这种行为是正确的,应该在每个请求上创建 ISession,并且应该处理 NH 上的每个操作。

如果您想将一个 ISession 用作单例,则应将存储库生活方式声明为单例。

我认为你的应用程序中应该有某种 SessionProvider o SessionFactory,也许你可以在单例会话中使用它。

HTH

【讨论】:

  • PerWebRequest 必须与 ISession 一起使用,否则不同用户的 NHibernate 会话会混淆!每次刷新会话时,您都不知道有多少用户的数据被刷新或者他们正在做什么。
  • 你真的不想把会话变成单例。
【解决方案3】:

我已经找出了问题所在,而且问题非常微妙。

我正在使用以下代码检索用户:

private MooseUser PageUser
  {
    get { return MooseUserRepository.Load(ApplicationSettings.UsernameFromWeb); }
}

ApplicationSettings.UsernameFromWeb 就 ASP.NET 而言检索当前用户的用户名。用户的用户名是用户表的自然键,但它不是主键!据我所知,一级缓存只对通过主键检索的对象有效。

编辑: 我通过创建一个属性来解决这个问题,该属性将加载的用户填充到 HttpContext.Current.Items 中,并在加载之前先检查那里,然后按照 this 文章进行加载。

【讨论】:

  • 您可以将用户 PK 而不是用户名存储为 FormsAuth 票证中的“用户名”。
  • 感谢您的出色提示,但在这种情况下没有好处。我通常对应用程序使用 NTLM 身份验证(因此用户名是用户的 Windows 登录名),但使用数据库来存储有关每个用户的附加信息。数据库中的表使用代理键。我知道我可以在 AD 中创建其他属性,但由于其他几个原因不能。
【解决方案4】:

您可以在映射中使用natural-id(Fluent NH 中的NaturalId)绕过二级缓存过期。

【讨论】:

  • 我没有使用二级缓存,否则这将是一个很棒的建议。为什么 natural-id 只能让对象在二级缓存中缓存???
猜你喜欢
  • 2011-01-10
  • 1970-01-01
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多