NServiceBus and NHibernate - Message Handler and Transactions - nhibernate

From my understanding NServiceBus executes the Handle method of an IMessageHandler within a transaction, if an exception propagates out of this method, then NServiceBus will ensure the message is put back on the message queue (up X amount of times before error queue) etc.. so we have an atomic operation so to speak.
Now when if I inside my NServiceBus Message Handle method I do something like this
using(var trans = session.BeginTransaction())
{
person.Age = 10;
session.Update<Person>(person);
trans.Commit()
}
using(var trans2 = session.BeginTransaction())
{
person.Age = 20;
session.Update<Person>(person);
// throw new ApplicationException("Oh no");
trans2.Commit()
}
What is the effect of this on the transaction scope?
Is trans1 now counted as a nested transaction in terms of its relationship with the Nservicebus transaction even though we have done nothing to marry them up? (if not how would one link onto the transaction of NServiceBus?
Looking at the second block (trans2), if I uncomment the throw statement, will the NServiceBus transaction then rollback trans1 as well? In basic scenarios, say I dump the above into a console app, then trans1 is independent, commit, flushed and won't rollback. I'm trying to clarify what happens now we sit in someone else's transaction like NServiceBus?
The above is just example code, im wouldnt be working directly with session, more like through a uow pattern.

If you mark your endpoint as transaction (.MsmqTransport().IsTransactional(true) or just AsA_Server) then the transactions will enlist into the one NServiceBus opened. What this means is that the commits you have inside your handler won't actually happen and the whole thing will either commit or rollback together - unless you specifically tell your transactions not to enlist in the ambient transaction.
Whether or not you work directly with the session or through a UoW, it looks like you want to do more than one for a given message - why? The message is already the natural UoW.

Related

Do we need to explicitly call Rollback after a failed database transaction

Most of the code implementing transactions I come across, call Rollback explicitly. Do we need to call Rollback? Doesn't Dispose call Rollback automatically? What's the purpose of explictly calling Rollback?
Thanks all.
public void DatabaseUpdate ()
{
using var MyTransaction = MyContext.Database.BeginTransaction () ;
if (MethodIsSuccessful ())
{
MyTransaction.Commit () ;
}
else
{
// MyTransaction.Rollback () ; // Not necessary, will be called by Dispose, right ?
}
}
Dispose() does not call Rollback(), it merely cleans up the transaction object and ensures the Entity Framework is no longer using that transaction.
So that brings us to the database. The Sql Server documentation states that
Depending on the current transaction isolation level settings, many resources acquired to support the Transact-SQL statements issued by the connection are locked by the transaction until it is completed with either a COMMIT TRANSACTION or ROLLBACK TRANSACTION statement. Transactions left outstanding for long periods of time can prevent other users from accessing these locked resources, and also can prevent log truncation.
So yes, you do need to call Rollback() explicitly, if that is your intention.

Suppressing NServicebus Transaction to write errors to database

I'm using NServiceBus to handle some calculation messages. I have a new requirement to handle calculation errors by writing them the same database. I'm using NHibernate as my DAL which auto enlists to the NServiceBus transaction and provides rollback in case of exceptions, which is working really well. However if I write this particular error to the database, it is also rolled back which is a problem.
I knew this would be a problem, but I thought I could just wrap the call in a new transaction with the TransactionScopeOption = Suppress. However the error data is still rolled back. I believe that's because it was using the existing session with has already enlisted in the NServiceBus transaction.
Next I tried opening a new session from the existing SessionFactory within the suppression transaction scope. However the first call to the database to retrieve or save data using this new session blocks and then times out.
InnerException: System.Data.SqlClient.SqlException
Message=Timeout expired. The timeout period elapsed prior to completion of the >operation or the server is not responding.
Finally I tried creating a new SessionFactory using it to open a new session within the suppression transaction scope. However again it blocks and times out.
I feel like I'm missing something obvious here, and would greatly appreciate any suggestions on this probably common task.
As Adam suggests in the comments, in most cases it is preferred to let the entire message fail processing, giving the built-in Retry mechanism a chance to get it right, and eventually going to the error queue. Then another process can monitor the error queue and do any required notification, including logging to a database.
However, there are some use cases where the entire message is not a failure, i.e. on the whole, it "succeeds" (whatever the business-dependent definition of that is) but there is some small part that is in error. For example, a financial calculation in which the processing "succeeds" but some human element of the data is "in error". In this case I would suggest catching that exception and sending a new message which, when processed by another endpoint, will log the information to your database.
I could see another case where you want the entire message to fail, but you want the fact that it was attempted noted somehow. This may be closest to what you are describing. In this case, create a new TransactionScope with TransactionScopeOption = Suppress, and then (again) send a new message inside that scope. That message will be sent whether or not your full message transaction rolls back.
You are correct that your transaction is rolling back because the NHibernate session is opened while the transaction is in force. Trying to open a new session inside the suppressed transaction can cause a problem with locking. That's why, most of the time, sending a new message asynchronously is part of the solution in these cases, but how you do it is dependent upon your specific business requirements.
I know I'm late to the party, but as an alternative suggestion, you coudl simply raise another separate log message, which NSB handles independently, for example:
public void Handle(DebitAccountMessage message)
{
var account = this.dbcontext.GetById(message.Id);
if (account.Balance <= 0)
{
// log request - new handler
this.Bus.Send(new DebitAccountLogMessage
{
originalMessage = message,
account = account,
timeStamp = DateTime.UtcNow
});
// throw error - NSB will handle
throw new DebitException("Not enough funds");
}
}
public void Handle(DebitAccountLogMessage message)
{
var messageString = message.originalMessage.Dump();
var accountString = message.account.Dump(DumpOptions.SuppressSecurityTokens);
this.Logger.Log(message.UniqueId, string.Format("{0}, {1}", messageString, accountString);
}

NHibernate Child Session not flushing properly

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();
}
}

"Using a single session in multiple threads is likely a bug" error in NHProf when using NServiceBus

When executing an NServiceBus handler that uses NHibernate for its data access operations, I am seeing an error that I am not sure if I need to be concerned with.
The handler has code that does something like this:
using (var tx = Session.BeginTransaction())
{
var accountGroup = _groupRepository.FindByID(message.GroupID);
accountGroup.CreateAccount(message.AccountNumber);
tx.Commit();
}
When I profile this process, I see the following lines:
enlisted session in distributed transaction with isolation level: Serializable
begin transaction with isolation level: Unspecified
SELECT ... FROM AccountGroups this_ WHERE this_.ID = 123
INSERT INTO Accounts ...
commit transaction
commit transaction
The first commit message is generated by my code when I call tx.Commit(). The second commit message, I believe occurs when we leave the Handle method of the handler and is called by NServiceBus. This second call to commit generates an alert in NHProf that states "Using a single session in multiple threads is likely a bug".
I don't think this is an issue, because there really is nothing to commit at that time, but am I doing some inappropriate here? I do want to run my code within a transaction, but when I do, I get this alert.
Any ideas?
This isn't an issue, what is happening is that NH Prof detects that the DTC commit is happening in another thread.
It should actually handle DTC commits properly, so I am not sure what is going on. At a guess, using both DTC commit and standard commit it confusing it.
I'll fix it.

Flush NHibernate whilst still allowing transaction rollback

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.