我刚刚阅读了我的 ASP.NET 页面之一的跟踪,我注意到每次需要用户时都会从数据库加载页面用户。由于每个 ISession 都应该缓存对象,因此我对此感到非常困惑。
从逻辑上讲,问题肯定是以下两件事之一:
-
ISession
的缓存无法正常工作
- 每次用户请求时,都会使用不同的
ISession
加载它code>
我假设问题是 2)。我使用 Castle Windsor 来管理对象生命周期,因此我发布了一些我正在使用的代码,以防有人可以帮助发现问题。 Castle Windsor 管理的类有:
-
MooseUserRepository
- 用于管理 MooseUser 实例(即本例中的页面用户)的存储库类
KctcUnitOfWork
- ISession 的包装器
MooseUserRepository
具有对 KctcUnitOfWork
的构造函数依赖关系,如下所示:
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 应该为我执行此操作。
任何人都可以冒险猜测出了什么问题吗?
I've just been reading the trace for one of my ASP.NET pages and I've noticed that the page user is being loaded from the database each time the user is required. Since each ISession
is supposed to cache objects, I'm really mystified about this.
Logically, the problem must surely be one of the following two things:
- The
ISession
's cache isn't working properly
- Each time the user is requested, it's being loaded using a different
ISession
I assume that the problem is number 2). I'm using Castle Windsor to manage object lifecycles so I've posted some of the code I'm using in case someone can help spot the problem. The classes being managed by Castle Windsor are:
MooseUserRepository
- a repository class for managing MooseUser instances (i.e. the page user in this case)
KctcUnitOfWork
- a wrapper for the ISession
MooseUserRepository
has a constructor dependency on KctcUnitOfWork
like this:
public MooseUserRepository(IUnitOfWork unitOfWork)
{
}
The config file looks like this:
<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"/>
Note the PerWebRequest
lifestyles.
The Castle Windsor container is simply a static property of a sort of utility class called Moose.Application
so it's always there:
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;
}
}
The page itself has a IMooseUserRepository instance like this:
private IMooseUserRepository _mooseUserRepository;
private IMooseUserRepository MooseUserRepository
{
get
{
if (_mooseUserRepository == null)
{
_mooseUserRepository = Moose.Application.WindsorContainer.Resolve<IMooseUserRepository>();
}
return _mooseUserRepository;
}
}
The user of the page is accessed by a property which looks like this:
private MooseUser PageUser
{
get { return MooseUserRepository.Load(ApplicationSettings.UsernameFromWeb); }}
It appears that these subsequent calls to PageUser
are causing the duplicate SQL commands:
txtSubject.Enabled = PageUser.CanHandleLegalWorks;
ddlDue.Enabled = PageUser.CanHandleLegalWorks;
Now obviously I can work around this problem by storing the loaded MooseUser
object in a private variable, but my understanding is that the ISession
is supposed to do this for me.
Can anyone hazard a guess as to what's going wrong?
发布评论
评论(4)
您陈述以下内容:
我认为您可能会将 Nhibernate 的一级(会话)级缓存与二级缓存混淆。
会话的制造和丢弃成本低廉。在 Web 应用程序中,您通常会为每个请求使用一个会话。第一次获取或加载实体后,它将被放入第一级缓存中,该缓存的范围为会话的生命周期。当会话在请求结束时关闭并处置时,您将无法再访问会话级缓存中的对象。事实上,每个用户都被不同的会话加载——这是完全正常的。
二级缓存的范围是会话工厂的生命周期。如果启用二级缓存,一旦通过主键加载实体,它就会存储在二级缓存中,并且可以被所有会话访问,而无需再次访问数据库,直到将其从缓存中删除。您将需要在每个实体的基础上显式启用缓存。这就是您正在寻找的行为。
进一步阅读:
编辑
您需要从 NHContrib 项目。 您可能需要使用 Asp.Net 缓存的 SysCache2,但如果您愿意,也可以使用 MemCached 或 Velocity 或其他几个。
我还建议您尝试一下 Nhibernate Profiler。我发现它对于探究 Nhibernate 的底层结构并了解 Nhibernate 正在做什么非常有价值。
You state the following:
I think you may be confusing Nhibernate's First (Session) Level Cache with the Second Level Cache.
Sessions are cheap to manufacture and throw away. In a Web app, you would typically use one session per request. Once you get or load an entity for the first time it is placed into the first level cache which is scoped to the lifetime of the session. When the Session is closed and disposed of at the end of the request you will no longer have access to objects in the session-level cache. Indeed, each User is being loaded by different sessions- this is perfectly normal.
The Second-Level cache is scoped to the lifetime of the Session Factory. If 2nd-level cache is enabled, once you load an entity by its primary key, it is stored in the 2nd-level cache and can be accessed by ALL sessions without hitting the database again until it is removed from the cache. You will need to explicitly enable caching on a per-entity basis. This is the behaviour you are looking for.
Further reading:
edit
You'll need to pick a Cache Provider from the NHContrib project. You probably want SysCache2 which uses the Asp.Net cache but you could go with MemCached or Velocity or a couple of others if you wanted to.
I also recommmend you give Nhibernate Profiler a try. I've found it invaluable in poking under the hood and finding out what Nhibernate's getting upto.
就像您在问题中注意到的那样,您正在为存储库使用 PerWebRequest 生活方式,因此将在每个请求时重新创建 ISession (由存储库使用)。
我发现这种行为是正确的,应该在每个请求上创建 ISession,并且应该对 NH 上的每个操作进行事务处理。
如果你想使用一个 ISession 作为单例,你应该将存储库生活方式声明为单例。
我认为你的应用程序中应该有某种 SessionProvider o SessionFactory ,也许你可以为单例会话处理它。
华泰
Like you have noticed in your question you are using a PerWebRequest lifestyle for the repository, so the ISession (used by repository) will be recreated on every request.
I find this behavior the right one, the ISession should be created on every request and every operation on NH should be transacted.
If you wanna use one ISession as singleton you should declare the repositories lifestyle as singleton.
I think you should have some sort of SessionProvider o SessionFactory in your app, maybe you could work on that for the singleton session.
HTH
我已经弄清楚了问题所在,而且这是一个非常微妙的问题。
我使用以下代码检索用户:
ApplicationSettings.UsernameFromWeb
就 ASP.NET 而言检索当前用户的用户名。用户的用户名是Users表的自然键,但它不是主键!据我所知,一级缓存仅适用于通过主键检索的对象。编辑:
我通过创建一个属性解决了这个问题,该属性将加载的用户填充到 HttpContext.Current.Items 中,并在按照 这篇文章。
I've worked out what the problem is and it is quite a subtle one.
I'm retrieving the user with the following code:
ApplicationSettings.UsernameFromWeb
retrieves the username of the current user as far as ASP.NET is concerned. The username of the user is a natural key for the Users table, but it's not the primary key! As far as I know, the first level cache only works for objects retrieved by primary key.Edit:
I solved this problem by creating a property which stuffs the loaded user in HttpContext.Current.Items and checks there first before loading as per this article.
您可以使用 natural-id (NaturalId 在Fluent NH) 在您的映射中绕过二级缓存过期,以应对这种情况。
You can use natural-id (NaturalId in Fluent NH) in your mapping to bypass second level cache expiration for just this situation.