I have a problem.
using (var tran = repository.Session.BeginTransaction())
{
try
{
repository.Save(entity);
tran.Comit();
}
catch(Exception)
{
tran.Rollback();
throw;
}
}
using (var tran = repository.Session.BeginTransaction())
{
try
{
repository.GetById(id);
tran.Comit();
}
catch(Exception)
{
tran.Rollback();
throw;
}
}
When I try to get an entity by ID after exception and tran.rollback() in the first using block, I get an update exception. So NHibernate is trying to update the entity from the first using block in the second using block.
Why? I did the tran.Rollback(). Must I do Session.Clear(), too?
According to Hibernate's API, when a Hibernate Session throws an exception, you must close the Session and create a new one. Also, when you roll back a Hibernate transaction, you must not later commit it or flush the Session - you must start over in a new Session.
In particular (and this is an implementation detail so don't rely on it), after a rollback, the Hibernate Session still has entities created/modified since the transaction began - Hibernate doesn't go through your entities and revert all changes you made. Therefore, if you roll back the transaction and then flush the Session, Hibernate will commit entity changes that you thought you rolled back. If you are going to play with fire by trying to hack around this behavior (such as by clearing the Session), beware. It's best to just start over with a new Session.
Related
I am currently fixing an old Windows Applications and encountered NHibernate error. I've read and tried few things on the net but end up error.
Here is my code for the ISession:
Public ReadOnly Property session() As ISession
Get
If IsNothing(m_session) Then
m_session = Factory.InitConfiguration.OpenSession()
End If
Return m_session
End Get
End Property
Here is my code for the save button:
Try
session.BeginTransaction()
SetParent(x_object)
'session.clear()
session.Flush()
session.SaveOrUpdate(x_object)
session.Transaction.Commit()
compObj.IsNew = False
Return True
Catch ex As Exception
AppServices.ErrorMessage = ex.Message
session.Transaction.Rollback()
Return False
Finally
'TBA
End Try
So the problem start here, I have this date column as DateTime and AttachmentList.
The current code doesn't have any problem until the user key in the year less than 1753. However the code catch the error properly and display the message and when the user continue to fix the year-typo, it'll still catch the error (while at the watch I already get the new value) until the user close the application and reopened it.
However if I uncomment the session.clear(), it will do just fine, the user may fix their typos and continue to save record, but then when the user do the other action lets say attachment, it will get another error. The attachment action as below:
Add Attachment
Click Save button
Add new attachment
Click Save button
New Error.
So please advice me on what need to be done. I've tried merge, I've tried update,save, evict but end up error. I think my problem is how I arrange the session is the main source of the problem.
It looks like the code you have to handle tries to go on using a session having experienced a failed flush or transaction commit.
This is an anti-pattern. From NHibernate reference:
NHibernate use might lead to exceptions, usually HibernateException.
This exception can have a nested inner exception (the root cause), use
the InnerException property to access it.
If the ISession throws an exception you should immediately rollback
the transaction, call ISession.Close() and discard the ISession
instance. Certain methods of ISession will not leave the session in a
consistent state.
...
The following exception handling idiom shows the typical case in NHibernate applications:
using (ISession sess = factory.OpenSession())
using (ITransaction tx = sess.BeginTransaction())
{
// do some work
...
tx.Commit();
}
Or, when manually managing ADO.NET transactions:
ISession sess = factory.openSession();
try
{
// do some work
...
sess.Flush();
currentTransaction.Commit();
}
catch (Exception e)
{
currentTransaction.Rollback();
throw;
}
finally
{
sess.Close();
}
You must ensure the code does not try to go on using the session after an exception.
Furthermore, it looks like the session stay opened while awaiting user interaction: this is not a recommended pattern. A NHibernate session is normally short lived. Opening a session is cheap.
An usual pattern is to open it when starting to process an event from user input and close it before ending the event processing, in order to not leaving it opened while the user is gone taking a coffee.
Now you may have a hard time changing the application session management, especially if the application retains references to entities and expects them to be still bound to an opened session after having waited for user interaction.
If the choice of keeping the session opened between user interaction was done for "leveraging first level cache" (session entity cache), consider activating second level cache instead.
I am trying to use NHibernate in combination with .NET's TransactionScope object. So far I have successfully used Oracle 11g and SQL Server 2008R2 with no issues. However, SQL Compact seems to fall on its face.
using (var scope = new TransactionScope(TranactionScopeOption.Required))
{
using (var session = _sessionFactory.OpenSession())
{
// The line below throws. I also tried passing in System.Data.IsolationLevel.ReadCommitted to no avail
using (var txn = session.BeginTransaction())
{
// Perform insert
txn.Commit();
}
}
scope.Complete();
}
This results in the following exception. I understand what this means, but I don't understand why it is attempting to create a nested transaction.
NHibernate.TransactionException: Begin failed with SQL exception ---> System.InvalidOperationException: SqlCeConnection does not support nested transactions.
at System.Data.SqlServerCe.SqlCeConnection.BeginTransaction(IsolationLevel isolationLevel)
at System.Data.SqlServerCe.SqlCeConnection.BeginDbTransaction(IsolationLevel isolationLevel)
at System.Data.Common.DbConnection.System.Data.IDbConnection.BeginTransaction(IsolationLevel isolationLevel)
at NHibernate.Transaction.AdoTransaction.Begin(IsolationLevel isolationLevel)
at NHibernate.Transaction.AdoTransaction.Begin(IsolationLevel isolationLevel)
Your code potentially produces 2 transactions:
1)new TransactionScope(TranactionScopeOption.Required)
2)session.BeginTransaction()
TransactionScope.Required "uses an ambient transaction if one already exists. Otherwise, it creates a new transaction before entering the scope. This is the default value". Your are guaranteed to have an ambient transaction by time session.BeginTransaction() gets hit, hence, making it a nested transaction.
If the transaction scope is not completed everything within its scope will be rolled back.
I think the answer lies in this question: The connection object can not be enlisted in transaction scope
"At a guess, TransactionScope needs to escalate to a distributed or nested transaction, neither of which is supported by CE."
I am just getting started using nHibernate, and some behavior is a bit disturbing, which is it seemingly does not throw exceptions when you try to perform some action when you have either coded it oncorrectly, or have a configuration error.
For example, I have some code:
ISessionFactory sessions = SessionManager.SessionFactory;
ISession session = sessions.OpenSession();
ITransaction tx1 = null;
tx1 = session.BeginTransaction();
IList<BusinessUnit> businessUnits = session.QueryOver<BusinessUnit>().List();
System.Diagnostics.Debug.WriteLine("Count: " + businessUnits.Count);
businessUnits[0].Name += "z";
session.Save(businessUnits[0]);
if (tx1 != null) tx1.Commit();
session.Close();
When I first coded it, I did not include the usage of a transaction:
tx1 = session.BeginTransaction();
if (tx1 != null) tx1.Commit();
So, apparently, if a transaction is not present, nHibernate doesn't (at least according to my configuration) automatically create one. Which is fine, I suppose.
But what bothers me is, if a transaction is required, and you try to perform a Save() without one, why doesn't it throw an exception?
(So far I have encountered another similar issue, I tried to load a collection, but apparently some error in my configuration prevented a successful load...so the code executes, but no exception indicating something has gone wrong).
Is there perhaps some config setting I am missing where exceptions are suppressed, or does a person have to unit test everything as no behavior can necessarily be assumed to work?
On a pre-existing object, a Save() is not automatically committed to the database, in the example above, it requires you to call:
Session.flush();
If one uses a transaction, then calling .Commit() on the transaction then invokes a flush().
So the reason no exception is thrown is because caching is default behavior by design (and if the programmer isn't aware of that then they will be very confused!!!)
We have implemented an auditing system via NHibernate event listeners. In our listener, we track all changes and write them out to our audit tables. To try and maximize performance, we have used Guid's for our audit tables so that we can batch our updates as much as possible.
We are writing out updates to a "child session" which we get like this:
protected ISession GetSession(AbstractEvent #event)
{
if (#event == null)
{
throw new ArgumentNullException("event");
}
ISession childSession = #event.Session.GetSession(EntityMode.Poco);
return childSession;
}
From the NHibernate documentation, this session should be a "child" session, that inherits all the attributes of it's parent - including the transaction.
Once we have created the entity, we save it to the session with:
childSession.Save(auditLogEntry);
All of this is called within a transaction and I would expect that the changes made to the childSession would flush once the transaction is committed. Unfortunately, nothing is happening and the changes are not flushing.
It should be noted that I can get this to work with a manual flush right after the save, but this won't work for us because the changes will no longer be batched (which will yield unacceptable performance).
At first I thought this behavior was limited to events, but I was able to abstract it out into a unit test to duplicate the behavior.
public void When_Saving_Audit_Log_Records_To_Child_Session_Flushes_When_Transaction_Committed()
{
ISession session = GetSession();
session.FlushMode = FlushMode.Commit;
ITransaction transaction = session.BeginTransaction();
ISession childSession = session.GetSession(EntityMode.Poco);
AuditLogEntry entry = CreateAuditLogEntry();
entry.AddAuditLogEntryDetail(CreateAuditLogEntryDetail());
entry.AddAuditLogEntryDetail(CreateAuditLogEntryDetail());
childSession.Save(entry);
transaction.Commit();
}
protected ISession GetSession()
{
return _sessionFactory.OpenSession();
}
I know that this is not your run of the mill NHibernate question, but if anyone has any experience or advice to share, I would love to hear it.
I am 2 seconds away from just writing the audit records out to a queue but I wanted to exhaust every possibility before giving up.
Thanks in advance,
Steve
The problem is from FlushMode.Commit: in this mode, NHibernate will only flush the session once, on committing transaction. So after it flush, it won't flush another time and any changes after the flushing will NOT be flushed.
To resolve this problem, you can either flush the session manually, or change to FlushMode.Auto. However, if you use Auto, beware of StackOverflowException with event listeners and/or interceptors, because with Auto, NHibernate will flush before querying, calling OnFlushDirty in as a result, so if you query something in OnFlushDirty, it will trigger another flushing, which then call OnFlushDirty again in a never ending loop. To prevent this situation, you must either temporarily change the FlushMode to Never, or implement a system to determine which changes has been processed to avoid processing the same change over and over.
We handle Auditing in the same way (with a GUID PK). When using Identity PK generator every call to Save issued an INSERT immediately, but when GUID is used the INSERTs are only executed during a Flush. We solved this years ago by patching the NHibernate source. In SessionImpl.cs in the Flush method (around line 1467) I added the following:
// Flush children when parent is flushed.
if (childSessionsByEntityMode != null) {
foreach (var childSession in childSessionsByEntityMode) {
childSession.Value.Flush();
}
}
I am trying to use NHibernate with legacy entities that are not mapped with NHibernate. On occasion this means that I need to manually flush NHibernate data to the database so that I don't receive foreign key exceptions when I try to connect the legacy entities with NHibernate-mapped entities.
A problem occurs when this takes place within a transaction that then needs to be rolled back. The data flushed from NHibernate does not rollback.
Is there anything I can do about this?
UPDATE
Still curious how to do this - I don't believe either of the answers given address the issue. I need to call Flush(). The question is, how do I rollback data that has been flushed?
check this: Force query execution without flush/commit
I seemed to have the same problem, i would flush and then i would rollback but some data would remain persisted in the database. However, there were some parts in my code that would call a commit, which cannot be rolled back. Consider the accepted answers' code snippet as the proper usage for transactions, flushes, rollbacks and commits and take into consideration that this pattern can be extended...
in a single unit of work (ie we consider a Request in a web application as a single unit of work and everything that happens in that request exists in a single transaction which onEndRequest is committed):
you call _sessionFactory.OpenSession(), _session.BeginTransaction(), _session.CommitTransaction() and _session.CloseSession() only once.
you can call _session.Flush() and _session.RollBackTransaction() as many times as you want, but Flush() is automatically called on Commit automatically. You may want to call a Flush when you need to make a query and ensure that the data fetched will not be stale.
Note that once a commit transaction is committed, all operations afterwards do not happen on that transaction. Instead NHibernate will create the necessary transaction under the hood (http://www.nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions) in which point
you already have problems tracking consistency and possibly logical integrity
If you really must call commit in the middle of your unit of work it is strongly advised to create a new transaction at that point so you can manage it explicitly
What's even better is to try out Nested Transactions will allegedly allow partial commits; you can rollback the "root" transaction and all changes will be reverted. I haven't really tested this feature of .NET and SQL Server, although the nested transaction in the database itself leaves a lot to be desired and i don't know how exactly ADO.NET instruments this feature.
points 1 to 4 have been tested with all versions of NHibernate starting from 1.2.
For the sake of formatting I allow myself to update tolism7's answer here.
use using and forget about transaction.Dispose() - the transaction will automatically be Dispose'd of at the end of the using block.
throw - don't throw ex because it means throwing away your stacktrace (see this post where it states "When the .NET Framework executes this statement: throw ex; it throws away all the stack information above the current function.")
.
public void CommitChanges()
{
using (var transaction = Session.BeginTransaction()) // <-- open scope
try
{
// do something
transaction.Commit();
}
catch (HibernateException)
{
transaction.Rollback();
_session.Close();
_session.Dispose();
throw; // <-- this way the stacktrace stays intact!
}
}
A VB.NET version of this piece of code can be found here.
When using transactions with NHibernate try to avoid using the Session.Flush() and instead use the transaction.Commit() which it calls the session.flush() internally.
If during the Commit() an error occurs and the transaction needs to be rolled back this can be addressed like this.
public static void CommitChanges()
{
ITransaction transaction = Session.BeginTransaction();
try
{
transaction.Commit();
}
catch (HibernateException ex)
{
transaction.Rollback();
//close and dispose session here
throw ex;
}
finally
{
transaction.Dispose();
}
}
Now, if a manual call to flush() or a call to commit() goes through successfully there isn't a way to roll back the transaction using NHibernate mechanisms.
Especially when calling the transaction.Commit() command the AdoTransaction created by NHibernate is then disposed right after the Commit() finishes so you cannot access it in order to roll back.
The code sample above allows you to catch errors that happen during commit and then roll back the transaction that has started already.
Now instead of calling the transaction.Commit() in the sample above you call the session.Flush() in my tests no data are saved in the Database as the transaction is never commited.
I have no idea how your code looks like but if you are calling in a pattern, as the above the code sample shows, the transaction.commit() instead of the Session.Flush() it should give you a way to achieve what you want.