Which exceptions should lead to closing of Nhibernate sessions? - nhibernate

I was reading that Nhibernate exceptions lead to invalid session state. So, my question is which exceptions should I handle and close and reopen the session.? And, should I reload all entities?
My scenario - I am opening a session in my presenter class for a form. And, I am using transactions like
using (ITransaction transaction = session.BeginTransaction())
{
foreach (var item in records)
{
session.Delete(item);
}
transaction.Commit();
}
so, should I do this?
using (ITransaction transaction = session.BeginTransaction())
{
foreach (var item in records)
{
session.Delete(item);
}
try
{
transaction.Commit();
}
catch(Exception ex)
{
rollback,
session.dispose
session = factor.opensession()
}
}

First off, I use the second option all the time. Now to the question, disposing and opening a new session is practically painless so I usually don't mind doing it "if any error occurs".

Related

NHibernate won't let me insert a model in a session if it was part of a failed transaction on that session

Why can't I just insert the model after I get an error back from the database when trying to insert it the first time:
Report report = null;
using (var session = SessionFactory.OpenSession()) {
try {
using (var transaction = session.BeginTransaction()) {
report = new Report();
session.SaveOrUpdate(report);//Exception: Name field required
transaction.Commit();
}
}
catch { }
try {
using (var transaction = session.BeginTransaction()) {
report.Name = "theName";
session.SaveOrUpdate(report);
//Causes Exception:
//Row was updated or deleted by another transaction (or unsaved-value
//mapping was incorrect): [ReportViewer.DataAccess.Models.Report#22]
transaction.Commit();
}
}
catch { }
}
But when I am updating an existing model, and I get an error, I can make my fixes (in this case set a Name) and just try to update again:
Report report = null;
using (var session = SessionFactory.OpenSession()) {
using (var transaction = session.BeginTransaction()) {
report = new Report();
report.Name = "theName";
session.SaveOrUpdate(report);
transaction.Commit();
}
}
using (var session = SessionFactory.OpenSession()) {
//get entity saved from previous session
report = session.Get<Report>(report.Id);
try {
using (var transaction = session.BeginTransaction()) {
report.Name = null;
session.SaveOrUpdate(report);//Exception: Name field required
transaction.Commit();
}
}
catch { }
try {
using (var transaction = session.BeginTransaction()) {
//updates and does not give an error
report.Name = "theName";
session.SaveOrUpdate(report);
transaction.Commit();
}
}
catch { }
}
When an exception triggered by the database occurs, the NHibernate session must be closed (disposed). It is not guaranteed to be consistent (internally or with the DB state) after an exception.
See the chapter on exception handling in the NHibernate reference.
As Oskar said, you should discard an NHibernate session after an exception occurs. However, the reason the insert fails is that you have already made the report persistent by calling SaveOrUpdate on it (you should use Save here). When you call SaveOrUpdate again on the same instance, NHibernate throws an exception because the object is already persistent. Rewriting the code as follows will probably allow the insert to succeed (but it's not recommended):
try {
using (var transaction = session.BeginTransaction()) {
report.Name = "theName";
transaction.Commit();
}
}
In the update example, calling SaveOrUpdate has no effect because the object became persistent when NHibernate loaded it. Understanding NHibernate's instance states and how to work with persistent objects is fundamental and widely misunderstood.
A far better approach is to validate your objects before saving them to the database.

Do WCF support Asynchronously operations' invoke within TransactionScope?

I am trying out the WCF Transaction implementation and I come up with the idea that whether asynchronous transaction is supported by WCF 4.0.
for example,
I have several service operations with client\service transaction enabled, in the client side, I use a TransactionScope and within the transaction, I create Tasks to asynchronously call those operations.
In this situation, I am assuming that the transaction is going to work correctly, is that right?
I doubt that very much. It appears that you if you are starting an ascync operation you are no longer participating on the original transaction.
I wrote a little LINQPad test
void Main()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
try
{
Transaction.Current.Dump("created");
Task.Factory.StartNew(Test);
scope.Complete();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Thread.Sleep(1000);
}
Console.WriteLine("closed");
Thread.Sleep(5000);
}
public void Test()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
Transaction.Current.Dump("test start"); // null
Thread.Sleep(5000);
Console.WriteLine("done");
Transaction.Current.Dump("test end"); // null
}
}
You'll need to set both the OperationContext and Transaction.Current in the created Task.
More specifically, in the service you'll need to do like this:
public Task ServiceMethod() {
OperationContext context = OperationContext.Current;
Transaction transaction = Transaction.Current;
return Task.Factory.StartNew(() => {
OperationContext.Current = context;
Transaction.Current = transaction;
// your code, doing awesome stuff
}
}
This gets repetitive as you might suspect, so I'd recommend writing a helper for it.

NHibernate: explicit update-call on attached entity

It seems that altough I have been using NHibernate for a while now, I still misunderstand some basic concepts of this ORM. Let's say I have a class called "Blog" and I load a persisted instance like so:
using (var tx = Session.BeginTransaction())
{
var myBlog = Session.Get(10);
tx.Commit();
}
If I now change a property of this instance, NHibernate seems to automatically detect the unsaved changes and will produce an UPDATE on transaction-commit.
This causes, that the following statements do exactly the same:
using (var tx = Session.BeginTransaction())
{
var myBlog = Session.Get(10);
myBlog.Title = "Changed title";
tx.Commit();
}
using (var tx = Session.BeginTransaction())
{
var myBlog = Session.Get(10);
myBlog.Title = "Changed title";
Session.Update(myBlog); // why is this necessary?
tx.Commit();
}
I don't see any difference with NHProf. So why does the explicit Update-method exists and when should I use it?
Entities are not always connected with session. For example you could have webservice with method, that accepts some entity, and updates in db:
[WebMethod]
void UpdatePerson(int id, string name){
using (var tx = Session.BeginTransaction(){
var person = new Person(id, name);
Session.Update(person);
tx.Commit();
}
}
This code executes update in database without issuing select.

NHibernate, TransactionScope and locking

I am trying to use TransactionScope with NHibernate in order to call several methods in one transactions. Data repository methods are like this:
public virtual void Save(T dataObject)
{
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
this.session.SaveOrUpdate(dataObject);
scope.Complete();
}
}
catch (Exception ex)
{
bool rethrow = ExceptionPolicy.HandleException(ex, "Data Layer Policy");
if (rethrow)
{
throw;
}
}
}
public T GetByNumber(string documentNumber)
{
T document = null;
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
document = this.Session.CreateCriteria(typeof(T))
.Add(Restrictions.Eq("Number", documentNumber))
.UniqueResult();
scope.Complete();
}
}
catch (Exception ex)
{
bool rethrow = ExceptionPolicy.HandleException(ex, "Data Layer Policy");
if (rethrow)
{
throw;
}
}
return document;
}
I wanted to test row/table locking in transactions so I made several unit tests and some console applications. Here is code from these console applications:
Application which does update:
const string DocumentNumber = "386774321";
Random randomGenerator = new Random();
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
using (BillingDocumentRepository billingDocumentRepository = new BillingDocumentRepository())
{
BillingOrderData orderData = billingDocumentRepository.GetByNumber(DocumentNumber);
orderData.Notes = randomGenerator.Next().ToString();
Console.WriteLine(string.Format("SECOND: {0}: Updated notes to {1}.", DateTime.Now.ToString("HH:mm:ss.fffff"), orderData.Notes));
Console.WriteLine(string.Format("SECOND: {0}: Updating order.", DateTime.Now.ToString("HH:mm:ss.fffff")));
Console.WriteLine(string.Format("SECOND: {0}: Going to sleep for 10000ms.", DateTime.Now.ToString("HH:mm:ss.fffff")));
Sleep(10000); // My custom sleep method because I didn't want to use Thread.Sleep for simulating long transaction
billingDocumentRepository.Save(orderData);
}
Console.WriteLine(string.Format("SECOND: {0}: Going to sleep for 10000ms.", DateTime.Now.ToString("HH:mm:ss.fffff")));
Sleep(10000);
Console.WriteLine(string.Format("SECOND: {0}: Completing transaction.", DateTime.Now.ToString("HH:mm:ss.fffff")));
scope.Complete();
}
Application which reads the same row in database:
while (true)
{
using (BillingDocumentRepository repository = new BillingDocumentRepository())
{
Console.WriteLine(string.Format("MAIN: {0}: Getting document.", DateTime.Now.ToString("HH:mm:ss.fffff")));
BillingOrderData billingOrderData = repository.GetByNumber("386774321");
Console.WriteLine(string.Format("MAIN: {0}: Got order with notes {1}.", DateTime.Now.ToString("HH:mm:ss.fffff"), billingOrderData.Notes));
Sleep(1000);
}
}
Problem is that first transaction (which updates row) doesn't lock row for reading at any moment. Second application is reading that row all the time with old value before scope.Complete() and than new value after that. How can I achieve locking with this model?
You should lock when reading. Locking later is "too late":
document = this.Session.CreateCriteria(typeof(T))
.Add(Restrictions.Eq("Number", documentNumber))
.SetLockMode(LockMode.Upgrade)
.SetTimeout(5)
.UniqueResult();
Or:
var doc = session.QueryOver<BillingDocument>()
.Where(c => c.Number== "2233445")
.Lock()
.Upgrade
.UnderlyingCriteria.
SetTimeout(5).
List().
FirstOrNull() as BillingDocument;
There is a session.Lock(object) method.
When you call session.Save(object), NHibernate isn't doing anything in the database until it gets flushed.
Flushing is done (depending on the flush mode, which is usually AutoFlush)
before queries (except Get and Load)
when calling flush explicitly
when committing the transaction (if the connection is created by NH I think)
When the session is flushed, the actual update, insert and delete operations are done on the database and locks are set.
In SQL Server, when the lock is set, the reading transaction is waiting until commit of the updating transaction. When it commits, it reads the committed values (when you are in "Read Committed" isolation).

Nhibernate: Handling an ITransaction Exception So That New Transactions Can Continue with same ISession

I have a list of 10 data objects that I want to insert/update to the database using NHibernate. If one throws an exception (say a primary key violation) I want to still insert/update the other 9. I rolled each object operation into its own atomic transaction, and roll back the transaction if there is an exception. Problem is that if a transaction does cause an exception and is rolled back, on the next transaction Nhibernate complains with the error: null id in Nexus.Data.PortfolioCorporateEntity entry (don't flush the Session after an exception occurs)
My main program is simple. It creates a session from a sessionfactory, creates the data access layer, does some work on the data objects and then tries to persist those data objects to the database.
sessionsManager = new NHibernateSessionManager();
session = sessionsManager.GetSession();
DALC = new NHibernateDataProvider(session);
…
foreach (var pce in pces)
{
try
{
DALC.UpdateOrAddObject<PortfolioCorporateEntity>(pce);
}
catch (Exception ex)
{
Console.WriteLine("Could not add Corporate Entity ID " + pce.CorporateEntity.CorporateEntityID.ToString());
}
}
This is the updateOrAdd procedure in my Nhibernate Data Access Layer, called 10 times for 10 objects.
public void UpdateOrAddObject<T>(T workObject)
{
using (ITransaction tx = mSession.BeginTransaction) {
try {
mSession.SaveOrUpdate(workObject);
mSession.Flush();
tx.Commit();
}
catch (Exception ex) {
tx.Rollback();
throw;
}
}
}
Just to make the point clear, the session is instantiated by the calling program and passed to the Data Access Layer object, constructor of which is below.
public NHibernateDataProvider(ISession session)
{
mSession = session;
}
This works fine except after the exception, it says don’t flush the session after exception. I’m not sure why – transaction was rolled back nicely and the database should be ready to accept another transaction no? What am I doing wrong?
It's not possible to re-use an NHibernate session after an exception is thrown. Quoting the documentation:
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.
So the answer is that you can't do what you're trying to do. You need to create a new session and re-try the updates there.
I clear the session and it continues normally
ISession session = NHibernateHelper.Session;
using (ITransaction transaction = session.BeginTransaction())
{
try
{
session.Update(user, user.UserID);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
session.Clear();
throw new DALException("Cannot update user", ex);
}
}
Thanks for the response. Just wanted to make sure it's done right. What you're saying is that my error handling should be simply changed to:
foreach (var pce in pces)
{
try
{
DALC.UpdateOrAddObject<PortfolioCorporateEntity>(pce);
}
catch (Exception ex)
{
Console.WriteLine("Could not add Corporate Entity ID " + pce.CorporateEntity.CorporateEntityID.ToString());
session.Close();
session = sessionsManager.GetSession();
DALC.Session = session;
}
}
Looks like this works just fine. Thanks.