我使用 NHibernate 工作了 4 年。此前我曾与“每次操作打开会话”反模式 http://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch02.html%20#%20session-per-operation。这些物体总是分离的。因此,为了坚持下去,我必须重新附加它们或将它们的值复制到附加的对象。这会导致许多行代码和许多“延迟初始化异常”。
最近,我研究了「对话模式」 http://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch02.html-long%20conversations我做了一个实现”春网 http://www.springframework.net/“基础设施。实施已提交给“jira.springsource” https://jira.springsource.org at the 问题 SPRNET-1431(“对话范围”和“每次对话会话”的解决方法) https://jira.springsource.org/browse/SPRNET-1431.
我没有制作“示例应用程序”,但如果您有兴趣,我可以这样做。
Hailton
补充回答:
我准备了示例应用程序并发布在SPRNET-1431“对话范围”和“每次对话会话”的解决方法 https://jira.springsource.org/browse/SPRNET-1431作为文件“Spring.Conversation.example.7z”。
下面,我写了一些解释来澄清(或澄清)我所做的事情。
该“示例应用程序”是对“Spring.NET”版本“1.3.0”中包含的“Spring.Data.NHibernate.Northwind”的修改,以使用Conversation。
目前,“Spring.Net”没有“会话范围”,也没有实现“扩展持久上下文”(“每个会话策略一个会话”)的概念。
在此示例应用程序中,目标是演示:
- 如何将对象实例保持在“对话范围”的模仿中。显示于
expression="@(convCustomer)['CustomerEditController']"
.
- 如何享受“扩展持久性上下文”。 “延迟加载错误”不再发生,并且重复调用
ISession.Get
让“同一条记录”不会造成对数据库的多次访问,更有效地利用NHibernate的缓存。对“CustomerList.aspx”的修改证明了这一点。要验证 Conversation 的有效性,请注释“App_Code\ConversationPage.cs”行this.Conversation.StartResumeConversation();
那么您将在单击“CustomerList.aspx”上的“+”按钮时看到错误“无法延迟初始化角色集合”。
重要提示:切勿在整个应用程序中使用单个会话(“HTTP 会话”的持续时间相同)。请记住,NHibernate 保留所有加载对象的缓存,如果会话保持很长时间,则该缓存往往会无限增长(限制是数据库记录的数量)。也就是说,每个对话应仅限于应用程序页面的一个子集,并且必须在与该子集交互结束时被丢弃(IConversationState.EndConversation()
)。建议:保留<property name="EndPaused" value="true"/>
在“Spring.Conversation.Imple.WebConversationManager”中,因此当开始对话时,其他对话将被丢弃。
附加信息:单元测试(“Spring.Northwind.IntegrationTests.2008”)不起作用。但这没有问题,因为它与支持对话所做的更改无关,事实上它们甚至在此之前就已经导致了错误。
“Spring.Data.NHibernate.Northwind”中的更改列表:
- Spring.Northwind.Web.References.2008
-
网络配置
-
模块,添加:
<add
name="ConversationModule"
type="Spring.Conversation.HttpModule.ConversationModule, Spring.Conversation"/>
<add
name="ConversationModule"
type="Spring.Conversation.HttpModule.ConversationModule, Spring.Conversation"/>
-
模块,已删除:
<add
name="OpenSessionInView"
type="Spring.Data.NHibernate.Support.OpenSessionInViewModule, Spring.Data.NHibernate21"/>
-
web.xml
-
模块配置
<!--Configuration for Spring HttpModule interceptor's-->
<object
name="HttpApplicationConfigurer"
type="Spring.Context.Support.HttpApplicationConfigurer, Spring.Web">
<property name="ModuleTemplates">
<dictionary>
<entry key="ConversationModule">
<!-- this name must match the module name -->
<object>
<!--
select "view source" in your browser on any page to
see the appended html comment
-->
<property name="ConversationManagerNameList">
<list element-type="string">
<value>conversationManager</value>
</list>
</property>
</object>
</entry>
</dictionary>
</property>
</object>
-
对话管理器
<!--Conversation Manager-->
<object
name="conversationManager"
type="Spring.Conversation.Imple.WebConversationManager, Spring.Conversation"
scope="session">
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
<property name="EndPaused" value="true"/>
</object>
-
客户对话
<!--
Conversation for 'CustomerEditor.aspx', 'CustomerList.aspx',
'CustomerOrders.aspx', 'CustomerView.aspx', and 'FulfillmentResult.aspx'
-->
<!--
Important: If the application had other parties
("management employees" for example), they should use another
conversation.
-->
<object
name="convCustomer"
type="Spring.Conversation.Imple.WebConversationSpringState, Spring.Conversation"
scope="session">
<property name="Id" value="convCustomer"></property>
<property name="TimeOut" value="0"></property>
<property name="ConversationManager" ref="conversationManager"></property>
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
<property name="DbProvider" ref="DbProvider"/>
<!--
Using workaround for 'conversation scope' to reference for
'CustomerEditController'. It is not as volatile as "request scope"
not as durable as the "session scope"
-->
<property name="['CustomerEditController']" ref="CustomerEditController"></property>
</object>
-
更改“CustomerEditController”范围,删除 [scope="session"] 并放置 [singleton="false"]:
<object
name="CustomerEditController"
type="NHibernateCustomerEditController"
singleton="false">
<constructor-arg name="sessionFactory" ref="NHibernateSessionFactory"/>
</object>
...
-
更改参考"CustomerEditController"
, 消除ref="CustomerEditController"
并把expression="@(convCustomer)['CustomerEditController']"
(模拟“对话范围”):
<!--
Using workaround for 'conversation scope' to reference for
'CustomerEditController'. It is not as volatile as "request scope"
not as durable as the "session scope"
-->
<object name="CustomerEditPage" abstract="true">
<property
name="CustomerEditController"
expression="@(convCustomer)['CustomerEditController']"/>
<property name="Conversation" ref="convCustomer"/>
</object>
<!--
Using workaround for 'conversation scope' to reference for
'CustomerEditController'. It is not as volatile as "request scope"
not as durable as the "session scope"
-->
<object type="CustomerView.aspx">
<property name="CustomerDao" ref="CustomerDao" />
<property
name="CustomerEditController"
expression="@(convCustomer)['CustomerEditController']" />
<property name="Conversation" ref="convCustomer"/>
<property name="Results">
<dictionary>
<entry key="EditCustomer" value="redirect:CustomerEditor.aspx" />
<entry key="CustomerList" value="redirect:CustomerList.aspx" />
</dictionary>
</property>
</object>
<!--
Using workaround for 'conversation scope' to reference for
'CustomerEditController'. It is not as volatile as "request scope"
not as durable as the "session scope"
-->
<object type="FulfillmentResult.aspx">
<property name="FulfillmentService" ref="FulfillmentService" />
<property
name="CustomerEditController"
expression="@(convCustomer)['CustomerEditController']" />
<property name="Conversation" ref="convCustomer"/>
<property name="Results">
<dictionary>
<entry key="Back" value="redirect:CustomerOrders.aspx" />
</dictionary>
</property>
</object>
<object type="Default.aspx">
<property name="Conversation" ref="convDefault"/>
<property name="Results">
<dictionary>
<entry key="CustomerList" value="redirect:CustomerList.aspx" />
</dictionary>
</property>
</object>
<!--Conversation for 'Default.aspx'-->
<!--
"convDefault" will have only one functionality: trigger the release
of other conversations when started (StartResumeConversation())
-->
<object
name="convDefault"
type="Spring.Conversation.Imple.WebConversationSpringState, Spring.Conversation"
scope="session">
<property name="Id" value="convDefault"></property>
<property name="TimeOut" value="0"></property>
<property name="ConversationManager" ref="conversationManager"></property>
<property name="SessionFactory" ref="NHibernateSessionFactory"/>
<property name="DbProvider" ref="DbProvider"/>
</object>
- 添加了“ConversationPage.cs”。支持对话的基本页面。
- CustomerList.aspx
- 允许在同一页面上列出“订单”,而不会出现“延迟初始化错误”。所有对象都保持附加到 ISession (NHibernate)。
- CustomerList.aspx.cs:
- 添加属性
IList<Customer> CustomersLoadedOncePerConvList
。列表仅加载一次,每个对话仅搜索数据库一次。
- 改变
Page_InitializeControls
供考虑CustomersLoadedOncePerConvList
.
- 方法
BtnShowOrders_Click
隐式地执行“延迟加载”Customer.Orders
.
- Change
??? : Spring.Web.UI.Page
to ??? : Spring.Web.UI.Page
on:
- 客户编辑器.aspx.cs
- 客户列表.aspx.cs
- 客户订单.aspx.cs
- 客户视图.aspx.cs
- FullfillmentResult.aspx.cs
- 默认.aspx.cs
-
Dao.xml
-
从“Default.aspx.cs”中删除(它们从未使用过):
-
customerDao
;
-
fulfillmentService
;
-
CustomerDao
;
-
Button1_Click(object sender, EventArgs e)
;
-
ProcessCustomer()
;
-
配置\Log4Net.xml。
...
<!--detail's about SQL's. To view sql commands on Logs\log.txt-->
<logger name="NHibernate.SQL">
<level value="DEBUG" />
</logger>
...
<!--detail's about Conversation-->
<logger name="Spring.Conversation">
<level value="DEBUG" />
</logger>