Our application uses a combination of
System.Transactions.TransactionScope and NHibernate.ITransaction for
two purposes:
1. Handle legacy tables that are not mapped with NHibernate in the
same ambient transaction as our NHibernate entities.
2. Use the second level cache for entities/tables that are mapped with
NHibernate.
Our application also uses an event listener derived from
DefaultSaveOrUpdateEventListener in order to make sure that we update
auditable properties before the entity is persisted. One of the
auditable properties must be populated from a table that is not mapped
in NHibernate. We wrap that database select with "using
TransactionScope" in order to get the value. We use
TransactionScopeOption.Required to make sure we are using the same
ambient transaction that is being used when the entity is persisted.
However, when we simply call ISession.Get() as in the below code,
which is wrapped in both a TransactionScope and an
NHibernate.ITransaction (for second level cache purposes), NHibernate
throws a System.ObjectDisposedException when the TransactionScope is
being completed and disposed.
Blog blogOne;
using (TransactionScope transactionScope = new
TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 0,
15)))
{
using (ISession session = this.OpenSession())
using (ITransaction txn =
session.BeginTransaction(Data.IsolationLevel.ReadCommitted))
{
blogOne = Repository.Current.Get<Blog>(session, BlogId); //Calls
session.Get<Blog>(BlogId)
log.Debug("Committing NHibernate.ITransaction.");
txn.Commit();
}
log.Debug("Committing TransactionScope.");
transactionScope.Complete();
}
When the TransactionScope is being completed and disposed, the
AdoNetWithDistributedTransactionFactory.DistributedTransactionContext
class's IEnlistmentNotification.Prepare() gets called. This method
calls Flush(), which starts cascading the entities in the session.
The cascading ends up saving the entities that were selected from the
database (but not modified), which calls our SaveOrUpdate event
listener. Our SaveOrUpdate event listener calls a method to populate
auditable properties on the entity being saved and wraps that database
select with "using TransactionScope" in order to get the value. The
select for the auditable data is below:
TimeSpan timeout = new TimeSpan(0, 0, 0, 15);
using (TransactionScope txnScope = new
TransactionScope(TransactionScopeOption.Required, timeout))
{
string select = string.Format("SELECT [Id] FROM [Application] WHERE
[Name] = '{0}'", applicationName);
CambridgeConnectionProvider connectionProvider = new
CambridgeConnectionProvider();
using (IDbConnection connection = connectionProvider.GetConnection())
using (IDbCommand command = connection.CreateCommand())
{
command.CommandText = select;
command.CommandTimeout = (timeout.Days * 3600 * 24) +
timeout.Hours
* 3600 + timeout.Minutes * 60 + timeout.Seconds;
command.CommandType = CommandType.Text;
using (IDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
application.Id = reader.GetGuid(0);
}
}
}
txnScope.Complete();
}
When the above TransactionScope is completed and disposed, the
AdoNetWithDistributedTransactionFactory.DistributedTransactionContext
class's IEnlistmentNotification.Prepare() gets called. It is at this
point that we get the ObjectDisposedException for the session:
[Level=ERROR] [Class=NHibernate.Transaction.ITransactionFactory]: DTC
transaction prepre phase failed
System.ObjectDisposedException: Session is closed!
Object name: 'ISession'.
at NHibernate.Impl.AbstractSessionImpl.ErrorIfClosed() in d:\CSharp
\NH\NH\nhibernate\src\NHibernate\Impl\AbstractSessionImpl.cs:line 207
at
NHibernate.Impl.AbstractSessionImpl.CheckAndUpdateSessionStatus() in d:
\CSharp\NH\NH\nhibernate\src\NHibernate\Impl
\AbstractSessionImpl.cs:line 199
at NHibernate.Impl.SessionImpl.get_PersistenceContext() in d:\CSharp
\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1138
at
NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource
session, IEntityPersister persister, Object key, Object anything) in d:
\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default
\AbstractFlushingEventListener.cs:line 211
at
NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource
session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default
\AbstractFlushingEventListener.cs:line 197
at
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent
event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default
\AbstractFlushingEventListener.cs:line 52
at
NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent
event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default
\DefaultFlushEventListener.cs:line 19
at NHibernate.Impl.SessionImpl.Flush() in d:\CSharp\NH\NH\nhibernate
\src\NHibernate\Impl\SessionImpl.cs:line 1487
at
NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext.System.Transactions.IEnlistmentNotification.Prepare(PreparingEnlistment
preparingEnlistment) in d:\CSharp\NH\NH\nhibernate\src\NHibernate
\Transaction\AdoNetWithDistributedTransactionFactory.cs:line 117
The mapping file for the entities is as follows:
<class name="Blog" batch-size="10">
<id name="Id" type="Guid" column="Id">
<generator class="assigned"/>
</id>
<property name="Author"/>
<property name="Name" not-null="true"/>
<component name="Created"
class="NHibernate.Test.NHSpecificTest.NHNestedTxns.Change,
NHibernate.Test">
<property name="ApplicationId" column="CreatedApplicationId"
type="Guid" not-null="false" />
<property name="Date" column="CreatedDate" type="datetime" not-
null="true"/>
</component>
<component name="Modified"
class="NHibernate.Test.NHSpecificTest.NHNestedTxns.Change,
NHibernate.Test">
<property name="ApplicationId" column="ModifiedApplicationId"
type="Guid" not-null="false" />
<property name="Date" column="ModifiedDate" type="datetime" not-
null="true"/>
</component>
<bag name="Posts" inverse="true" cascade="all-delete-orphan, merge"
lazy="false">
<key column="BlogId"/>
<one-to-many class="Post"/>
</bag>
</class>
<class name="Post">
<id name="Id" type="Guid" column="Id">
<generator class="assigned"/>
</id>
<property name="Title" not-null="true"/>
<property name="Body"/>
<component name="Created"
class="NHibernate.Test.NHSpecificTest.NHNestedTxns.Change,
NHibernate.Test">
<property name="ApplicationId" column="CreatedApplicationId"
type="Guid" not-null="false" />
<property name="Date" column="CreatedDate" type="datetime" not-
null="true"/>
</component>
<component name="Modified"
class="NHibernate.Test.NHSpecificTest.NHNestedTxns.Change,
NHibernate.Test">
<property name="ApplicationId" column="ModifiedApplicationId"
type="Guid" not-null="false" />
<property name="Date" column="ModifiedDate" type="datetime" not-
null="true"/>
</component>
<many-to-one name="Blog" cascade="save-update, merge" class="Blog"
column="BlogId" not-null="true"/>
</class>
I have a unit test in the NHibernate.Lite test solution that
reproduces the issue. The log entries leading up to the error that
include log values from the above code snippets is as follows:
[Level=DEBUG]
[Class=NHibernate.Test.NHSpecificTest.NHNestedTxns.Fixture]:
Committing TransactionScope.
[Level=DEBUG] [Class=NHibernate.Impl.SessionImpl]: before transaction
completion
[Level=DEBUG] [Class=NHibernate.Transaction.ITransactionFactory]:
[session-id=b72f79d6-2bea-4837-98dc-a0e9da986045] Flushing from Dtc
Transaction
[Level=DEBUG]
[Class=NHibernate.Event.Default.AbstractFlushingEventListener]:
flushing session
[Level=DEBUG]
[Class=NHibernate.Event.Default.AbstractFlushingEventListener]:
processing flush-time cascades
[Level=INFO ] [Class=NHibernate.Engine.Cascade]: processing cascade
NHibernate.Engine.CascadingAction+SaveUpdateCascadingAction for:
NHibernate.Test.NHSpecificTest.NHNestedTxns.Blog
[Level=INFO ] [Class=NHibernate.Engine.Cascade]: cascade
NHibernate.Engine.CascadingAction+SaveUpdateCascadingAction for
collection: NHibernate.Test.NHSpecificTest.NHNestedTxns.Blog.Posts
[Level=DEBUG] [Class=NHibernate.Engine.CascadingAction]: cascading to
saveOrUpdate: NHibernate.Test.NHSpecificTest.NHNestedTxns.Post
[Level=DEBUG]
[Class=NHibernate.Test.NHSpecificTest.NHNestedTxns.SaveEventListener]:
Saving or updating entity
[NHibernate.Test.NHSpecificTest.NHNestedTxns.Post].
[Level=DEBUG]
[Class=NHibernate.Test.NHSpecificTest.NHNestedTxns.SaveEventListener]:
Calling Repository.OnSaving event for entity
[NHibernate.Test.NHSpecificTest.NHNestedTxns.Post].
[Level=DEBUG]
[Class=NHibernate.Test.NHSpecificTest.NHNestedTxns.Fixture]: Handling
OnSavedOrUpdated event for Post.
[Level=DEBUG]
[Class=NHibernate.Test.NHSpecificTest.NHNestedTxns.Fixture]: Finding
application id for [Cambridge.Two].
[Level=DEBUG] [Class=NHibernate.Impl.SessionImpl]: transaction
completion
[Level=DEBUG] [Class=NHibernate.Impl.SessionImpl]: [session-
id=b72f79d6-2bea-4837-98dc-a0e9da986045] executing real Dispose(True)
[Level=DEBUG] [Class=NHibernate.Impl.SessionImpl]: closing session
[Level=DEBUG] [Class=NHibernate.AdoNet.AbstractBatcher]: running
BatcherImpl.Dispose(true)
[Level=ERROR] [Class=NHibernate.Transaction.ITransactionFactory]: DTC
transaction prepre phase failed
System.ObjectDisposedException: Session is closed!
I am not sure if this is an NHibernate issue or an issue with our
event handler or mapping files (cascade options). I don't know why
the entities would be saved if they weren't modified. Any suggestions
are appreciated. Thanks.
--
You received this message because you are subscribed to the Google Groups
"nhusers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/nhusers?hl=en.