65.9K
CodeProject 正在变化。 阅读更多。
Home

调试 NHibernate:会话管理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (4投票s)

2011 年 10 月 13 日

MIT

9分钟阅读

viewsIcon

34074

downloadIcon

344

本文详细介绍了 NHibernate 中的 Session 管理。它展示了如何使用调试器检查 Session 属性,并确认 Session 是否以预期的方式打开和关闭。

引言

NHibernate Session 是您对数据存储执行所有操作的入口。它还充当第一级缓存,最大限度地减少应用程序和数据库服务器之间的往返次数。正如 NHibernate 文档所述,Session 是“一个**单线程**、**短暂**的对象,代表应用程序和持久存储之间的对话”。请特别注意粗体字:单线程和短暂。在 Web 开发世界中,单线程基本上意味着您不应在两个或多个并发请求中使用同一个 Session 实例。而短暂则指示您不要“过度使用”Session 实例,以免其成为数据库的副本。开发人员有责任满足这些要求并实现正确的 Session 管理。幸运的是,已经为此目的创建了多种模式:每个请求一个 Session、每个对话一个 Session、每个线程一个 Session 等,并且可以在 Internet 上轻松找到它们的实现示例。但即使您采用其中一个示例并将其改编到您的应用程序需求,也可能会发生 Session 的行为不如您预期的那样:对象未持久化、事件未触发,或者您收到 LazyLoadException。这些棘手的错误通常很难解决,尤其是在对象生命周期由某种形式的控制反转容器管理的这类企业级应用程序中。在本文中,我想向您展示如何准确找出创建和销毁 Session 的时间(以及由谁创建和销毁)以及可以从其属性中检索哪些信息。

调查 NHibernate 内部的常规方法是打开其细粒度日志记录,并检查 log4net appenders 的输出。但是,这并不是我想在本文中介绍的方法(如果您想阅读关于详细 NHibernate 日志记录的文章,请发表评论)。今天,我们将重点介绍如何使用 Visual Studio 调试器来检查 Session 管理。我已经准备了一个示例应用程序,您可以在上面练习调试过程(配置细节将在文章末尾提供)。

准备 NHibernate 符号

要能够调试 NHibernate 源代码,调试器必须知道如何找到它。如果您使用的是 NHibernate 的 3.0.0 版本,您可以使用 http://www.symbolsource.org 来配置 Visual Studio 使用它们的符号存储。如果您的 NHibernate 版本不同,您需要自己编译 NHibernate 并引用项目中的二进制文件,或者使用 **sourcepack** 脚本。我将在此描述第二种方法(尤其是我本人是 sourcepack 的作者,我需要以某种方式宣传它;))。要使用 **sourcepack**,您需要安装 Windows 调试工具(您可以从 WDK 镜像安装)和 powershell v2。下一步是下载 NHibernate 二进制文件(您应该已经在应用程序 bin 文件夹中有了它们)以及 NHibernate 源代码,从它们的 sourceforge 页面下载。

现在,我们准备好为 NHibernate 符号(PDB)文件和压缩的源代码建立索引。打开 powershell 控制台并输入

.\sourcepack.ps1 -sourcesRoot "d:\CSharp\NH\NH\nhibernate\" -sources 
	C:\src\NHibernate-3.1.0.GA-src.zip -symbols C:\temp\sample-app\bin 
	-archiverCommandPath C:\bin\7za\7za.exe -verbose

其中

  • d:\CSharp\NH\NH\NHibernate\ 是 NHibernate 在计算机上构建的源根目录(您可以使用 Windows 调试工具中的 srctool.exedbh.exe 检查此路径 - 请参阅我的 博客了解用法示例)
  • C:\src\NHibernate-3.1.0.GA-src.zip 是您放置下载的源代码的位置
  • C:\temp\sample-app\bin 是您的应用程序二进制文件(DLL + PDB)所在的位置
  • C:\bin\7za\7za.exe sourcepack 需要 7za 命令的路径,调试器将使用该命令来提取必要的源文件(sourcepack 发布版已包含 7za.exe 应用程序,只需将其复制到某个位置即可)

运行命令后,您应该会看到一些黄色(详细)消息,NHibernate PDB 文件现在已与源代码 zip 文件链接。现在是时候测试调试器是否能够成功提取它们了。为此,您应该再次使用 srctool.exe(它位于 Windows 调试工具安装的 srcsrv 文件夹中)

srctool -l:*AbstractSessionImpl.cs NHibernate.pdb -x -d:c:\temp\nh-test

上述命令的输出应该是

c:\temp\nh-test\src\NHibernate\Impl\AbstractSessionImpl.cs

NHibernate.pdb: 1 source files were extracted

如果您看到不同的内容,您可能在索引过程中犯了错误,需要重新运行它(我知道这很麻烦,但我计划让 sourcepack 的使用更方便一些)。如果前面的句子不适用于您的情况,您就可以启动 Visual Studio 并开始使用您全新的符号文件调试 NHibernate 源代码了:)。

在 Session 打开时中断

在 Session 创建过程中中断的最佳位置是 SessionImpl 构造函数。您只需从 Debug 菜单中选择 _New Breakpoint_,然后选择 _Break at function..._(或 Ctrl + B),并在 Function 框中输入 NHibernate.Impl.SessionImpl.SessionImpl。调试器可能会抱怨它此时无法绑定断点 - 请忽略此消息,并确认您要设置断点。CLR 使用 .ctor 作为构造函数名称而 VS 偏爱类名(尽管有时绑定断点后会显示 NHibernate.Impl.SessionImpl..ctor 这样的位置,所以不用担心 - 一切正常;))。另外一点需要注意 - 在 Visual Studio 中,当您创建函数断点时,它将应用于所有名称与您提供的模式匹配的函数。在我们的例子中,由于 SessionImpl 对象有三个构造函数,我们将有三个子断点(请看第一个截图)。没有规定何时创建断点,但如果您想跟踪所有 Session,可能希望在开始调试过程之前设置它。

现在,让我们启动调试器,并尝试在应用程序中执行一些需要新 Session 的操作。启动时,请查看断点对话框(Debug -> Windows -> Breakpoints),看看我们的断点是如何神奇地变得可用的。

Bound SessionImpl breakpoints

如果您正确准备了符号和源代码,现在您应该很高兴看到

Breakpoint hit

断点命中后,让我们检查线程调用堆栈

Open session call stack

我用红色矩形标记了两个帧,它们实际上告诉了我们一切。Session 是在 ApplicationBeginRequest_ 事件中由默认的 ISessionFactory 实现(SessionFactoryImpl)打开的。最棒的是,您可以将 _this_ 添加到监视窗口并检查 Session 的所有属性。此外,您还可以在调用堆栈中设置断点,然后返回到 ISessionFactory 实例并检查其属性(例如定义 Session 持续时间及其存储位置的 currentSessionContext)。

在 Session 关闭时中断

在这一段中,我们将逐步进行 Session 关闭过程,以找出可能出现问题的地方。关闭过程可分为两部分:刷新和实际关闭。在刷新期间,NHibernate 将 Session 的状态与数据库同步,准备要执行的有序查询集合。我们将从在 NHibernate.Impl.SessionImpl.Flush 上设置一个未解析的断点开始分析。在启动调试器(F5)后,我们的断点应该会生效并停止在以下代码的执行:

Frame1

图片的上半部分包含当前的调用堆栈,下半部分是代码。通常,您应该首先检查 Flush 是否是从您计划的位置调用的(在我的例子中,它是在响应 Application_EndRequest 事件提交事务时调用的 - 所以这不足为奇)。我们将对 flushEventListener[i].OnFlush(new FlushEvent(this)) 这一行感兴趣,因为这是繁重工作发生的地方。如果您没有向 NHibernate 提供任何自定义的 IFlushListener 实现,将使用默认的(DefaultFlushListener)。

Frame2

标记的行是最有趣的。FlushEverythingToExecution 检查 Session 对象的状态并准备查询,而 PerformExecutions(顾名思义)则将查询执行到数据库。此时,检查 Session 实体字典中存储了哪些对象及其属性可能会很有趣。要完成此操作,请展开 @event.Session 对象并选择 SessionImpl 对象的 PersistenceContext 属性,或者直接将 ((NHibernate.Impl.SessionImpl)(@event.Session)).PersistenceContext 表达式添加到您的监视窗口。浏览 EntitiesByKeyCollectionsByKey 属性。

Frame3

FlushEntities 上方的注释准确地解释了此方法的作用:

    // 1. detect any dirty entities
    // 2. schedule any entity updates
    // 3. search out any reachable collections
    private void FlushEntities(FlushEvent @event)
    {
    // other code goes here ...

在下面的代码中,您可以看到它遍历 EntityEntries 集合,为每个具有正确状态的实体触发 OnFlushEntity 事件:

Frame4

上面选定的行可能是设置条件断点的有趣位置,该断点过滤我们感兴趣的条目,例如 entry.Id.Equals(1)entry.EntityName.Equals("NHibernate.Example.Web.Models.Item")。我们要访问的下一个地方是 DefaultFlushEntityEventListener 及其 OnFlushEntity 处理程序:

Frame5

第一个红色矩形选择的代码检查实体状态 - 无论其属性自加载以来是否已更改,或者它已被删除,或者它是一个新实体。如果您发现对象未更新,则可以调试此代码,但在您动手之前,可以先检查 SessionImpl.ActionQueues 是否被此方法更新。ActionQueues 顾名思义,包含必须执行以将 Session 与数据库同步的操作。它们根据类别分组到队列中:插入、更新、删除、集合创建、集合更新、集合删除。此时,您可能希望检查这些集合是否包含有效数量的元素(甚至正确数量的 _脏_ 属性)。在填充操作队列后,就该将它们转换为 SQL 查询了。这在 DefaultFlushingEventListner(或者更确切地说是 AbstractFlushingEventListener)的 PerformExecutions 方法中完成:

Frame6

ExecuteActions 内部调用 NHibernate.Engine.ActionQueue.ExecuteActions 方法,该方法遍历所有队列中的所有操作,调用它们的 Execute 方法,该方法内部准备 SQL 查询(PersisterUpdate 方法),并(如果配置了)将它们放入批处理中或立即执行。

以上将是 Session 刷新过程的描述。还有一件事需要一些解释 - Session 的关闭。幸运的是,您这些可能已经厌倦的读者,这个方法相当简单,其主要作用是关闭所有打开的连接并处理内部 NHibernate 对象(如批处理器、事务包装器等)。

示例代码

要运行示例代码,您需要 SQL Server 实例(Express 版本即可)。zip 文件包含一个 db.sql 脚本,其中包含设置示例数据库所需的命令。请不要将示例代码视为理想的 MVC 实现 - 它包含的元素尽可能少,所有代码都放在 App_Code 目录中,只是为了加快部署速度。

备注

在本文中,我试图向您展示如何调试您的 NHibernate 应用程序以检查 Session 的一些特性。当您的 Session 包含数百个实体,或者出现性质不同的问题(如无效的事务管理)时,整个调试过程可能会变得更加复杂。尽管如此,我希望您喜欢阅读这篇文章,并希望它能在您未来的 NHibernate 难题中有所帮助。

本文基于我博客 http://lowleveldesign.wordpress.com 上的帖子。如果您觉得它有趣,请访问我的博客,看看还有什么内容:)。

历史

  • 2011 年 10 月 12 日:初始版本
© . All rights reserved.