I have a problem with Session.Clear(), as I understand it this statement should completely reset all changes made in the UOW, the cache etc. The problem is that is does not. My scenario is illustrated in the test below. I add to items, there is a dependency between them I then try to delete the item which the other item is dependent upon. This will cause an exception which is correct. I then clear the session. Finally I try add a new item to the database, when flushing NHibernate will once again try to execute the failing delete statement. Am I misunderstanding the use of Session.Clear()? Or am I missing something else here?
[Fact]
public void Verify_Cant_Clear_Delete()
{
var session = SessionFactory.OpenSession();
var category = new ManufacturerCategory { Name = "category" };
var man = new Manufacturer { Category = category, Name = "man" };
session.Save(category);
session.Save(man);
session.Flush();
try
{
// this will cause
// NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available]
// ---> System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "ManufacturerCategoryId".
// The conflict occurred in database "LabelMaker-Tests", table "dbo.Manufacturers", column 'Category_id'.
session.Delete(category);
session.Flush();
}
catch (Exception ex)
{
// This should clear the session
session.Clear();
}
try
{
var category2 = new ManufacturerCategory { Name = "category 2" };
session.Save(category2);
session.Flush();
}
catch(Exception ex)
{
// this will cause ONCE AGAIN cause
// NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available]
// ---> System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint "ManufacturerCategoryId".
// The conflict occurred in database "LabelMaker-Tests", table "dbo.Manufacturers", column 'Category_id'.
Assert.True(false);
}
}
You should be using transactions and rollback the transaction on an hibernate exception because...
Certain methods of ISession will not leave the session in a consistent state.
http://nhibernate.info/doc/nh/en/index.html#manipulatingdata-exceptions
or let the transaction be implicitly rolled back via dispose()...
using (ISession sess = factory.OpenSession())
using (ITransaction tx = sess.BeginTransaction())
{
// do some work
...
tx.Commit();
}
I'd guess that the Delete() method is one of the inconsistent state methods hinted at in the above quote...
Your assumptions about ISession.Clear() are not correct.
In particular, as soon as an exception happens, you must discard the session. Clear() will not fix the state (check out 9.8 Exception Handling)
Also, as dotjoe mentioned, you should be using a transaction to do your work.
Related
This is a sample code. where I am doing some test
Get entities
Delete a entity
Rollback transaction.
Change entity
Refresh entity
Get entities
I am getting this exception while excuting below code : instance was not in a valid state
ISession session = sessionFactory.OpenSession();
var list1 = session.Query<Asset>().ToList();
ITransaction transaction = session.BeginTransaction();
session.Delete(list1[0]);
transaction.Rollback();
transaction.Dispose();
list1[0].Name = "Test";
session.Refresh(list1[0]);
var list2 = session.Query<Asset>().ToList();
if I call refresh two times. it does not give any issue. it works fine.
try
{
session.Refresh(list1[0]);
}
catch (Exception)
{
session.Refresh(list1[0]);
}
Could you please tell me about your view and suggestion that what is wrong here.
I think the problem is with your handling of rollback and exception. After a rollback or an exception, the in-memory state of objects are likely no longer consistent with their persisted state, so the session is not safe for use anymore without any cleanup. It's suggested that after an exception, you should rollback any transaction, then either discard the session, or clear it using session.Clear(). The same applies with rollback, you should either start a new session, or clear it and discard all existing objects, or the inconsistencies will cause a lot of troubles.
I've got a WCF operation conceptually like this:
[OperationBehavior(TransactionScopeRequired = true)]
public void Foo()
{
try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); }
catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
}
If something goes wrong (say, foreign key constraint violaion) in executing the DAL code, control passes to the catch block as I'd expect. But when the method returns, it seems the transaction scope has sniffed out that the transaction failed, and it decides it better throw an exception to make sure to notify the caller about it.
In turn my client application does not get the receipt I want to return, but rather an exception:
System.ServiceModel.FaultException:
The transaction under which this method call was executing was asynchronously aborted.
What is wrong with my design?
I could have the service not catch anything, but this has it's own problems as the service needs to use exception shielding and the client (a batch tool internal to the system) needs to log the error information. The service logs errors too, but not in the same way and to the same place as the batch.
Be careful here! If you set TransactionAutoComplete=true then if the service returns normally the transaction will be committed. Only if there is an unhandled exception (which for the most part you don't have because you are catching exceptions and returning a receipt message) will the transaction be rolled back. See http://msdn.microsoft.com/en-us/library/system.servicemodel.operationbehaviorattribute.transactionautocomplete.aspx.
Think about a scenario where you successfully executed some DAL calls but some other exception (e.g. NullReferenceException) occurs. Now the transaction will be committed when the method completes because no unhandled exception has occurred but the client receives an ErrorReceipt.
For your scenario, I think you will have to manage the transactions yourself. For example:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo()
{
// Create TransactionScope using the ambient transaction
using (var scope = new TransactionScope() )
{
try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); scope.Complete(); }
catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
}
}
You could eliminate boilerplate code by creating a helper method that wraps it all within the transaction or you could use policy injection/interception/aspects to manage transactions.
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo()
{
return ProcessWithTransaction(() =>
{
DAL.Foo();
return Receipt.CreateSuccessReceipt();
}
, (ex) =>
{
return Receipt.CreateErrorReceipt(ex);
}
);
}
T ProcessWithTransaction<T>(Func<T> processor, Func<Exception, T> exceptionHandler)
{
using (var scope = new TransactionScope())
{
try
{
T returnValue = processor();
scope.Complete();
return returnValue;
}
catch (Exception e)
{
return exceptionHandler(e);
}
}
}
You mention that you need to use exception shielding. If you are not averse to throwing faults when an error occurs then you could use Enterprise Library Exception Handling Block's exception shielding which also lets you log the information on the way out (if you desire).
If you decided to go that route your code would look something like this:
[OperationBehavior(TransactionScopeRequired = true)]
public void Foo()
{
// Resolve the default ExceptionManager object from the container.
ExceptionManager exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();
exManager.Process(() =>
{
DAL.Foo();
return Receipt.CreateSuccessReceipt();
},
"ExceptionShielding");
}
Enterprise Library (via configuration) would then catch any exceptions and replace them with a new FaultException that is returned to the client.
[OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
Presumably because the transaction is now rolled back as soon as the error occurs, rather than asynchronously when the scope goes out of scope :D, this behaves like I expected things to behave originally, and I can leave my design as it is.
(I had already written up the question when trying this occured to me. Hopefully posting it Q&A style will be more helpful than not posting the question at all.)
I've just start studying NHibernate 2 days ago, and I'm looking for a CRUD method that I've written based on an tutorial.
My insert method is:
using (ISession session = Contexto.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Save(noticia);
transaction.Commit();
session.Close();
}
The complete code of "Contexto" is here: http://codepaste.net/mrnoo5
My question is: Do i really need to use ITransaction transaction = session.BeginTransaction() and transaction.Commit();?
I'm asking this because I've tested run the web app without those two lines, and I've successfully inserted new records.
If possible, can someone explain me too the purpose of Itransaction and the method Commit?
Thanks
This is the proper generic NHibernate usage pattern:
using (ISession session = sessionFactory.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
//Do the work here
transaction.Commit();
}
All of that is required to ensure everything works as expected (unless you use additional infrastructure)
Closing the session or doing anything with the transaction besides committing is redundant, as the Dispose methods of the session and the transaction takes care of cleanup, including rollback if there are errors.
It's important to note than doing anything with the session after an exception can result in unexpected behavior, which is another reason to limit explicit exception handling inside the block.
ITransaction transaction = session.BeginTransaction() is not necessary as you found out by testing.
But imagine this, what happens when your session throws an exception? how would you get back your changes to the database?
The following is quote from Nhib...
A typical transaction should use the following idiom:
ISession sess = factory.OpenSession();
ITransaction tx;
try
{
tx = sess.BeginTransaction(); //do some work ...
tx.Commit();
}
catch (Exception e)
{
if (tx != null)
tx.Rollback();
throw;
}
finally
{
sess.Close();
}
If the ISession throws an exception, the transaction must be rolled back
and the session discarded. The internal state of the ISession might not be
consistent with the database after the exception occurs.
NHibernate.ISessionFactory
Well you call Commit() on your transaction it saves all the changes to the database.
Do i really need to use ITransaction transaction = session.BeginTransaction() and transaction.Commit();?
yes it is considered good practice using transactions for everything even simple reads.
tested run the web app without those two lines, and I've successfully inserted new records.
that because the session will commit the changes when it is disposed at the end of the using statement.
anyway this is how i would right the save :
using (ISession session = Contexto.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
session.Save(noticia);
transaction.Commit();
}
catch(HibernateException)
{
transaction.Rollback();
throw;
}
}
}
I made a test class against the repository methods shown below:
public void AddFile<TFileType>(TFileType FileToAdd) where TFileType : File
{
try
{
_session.Save(FileToAdd);
_session.Flush();
}
catch (Exception e)
{
if (e.InnerException.Message.Contains("Violation of UNIQUE KEY"))
throw new ArgumentException("Unique Name must be unique");
else
throw e;
}
}
public void RemoveFile(File FileToRemove)
{
_session.Delete(FileToRemove);
_session.Flush();
}
And the test class:
try
{
Data.File crashFile = new Data.File();
crashFile.UniqueName = "NonUniqueFileNameTest";
crashFile.Extension = ".abc";
repo.AddFile(crashFile);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsInstanceOfType(e, typeof(ArgumentException));
}
// Clean up the file
Data.File removeFile = repo.GetFiles().Where(f => f.UniqueName == "NonUniqueFileNameTest").FirstOrDefault();
repo.RemoveFile(removeFile);
The test fails. When I step in to trace the problem, I found out that when I do the _session.flush() right after _session.delete(), it throws the exception, and if I look at the sql it does, it is actually submitting a "INSERT INTO" statement, which is exactly the sql that cause UNIQUE CONSTRAINT error. I tried to encapsulate both in transaction but still same problem happens. Anyone know the reason?
Edit
The other stay the same, only added Evict as suggested
public void AddFile<TFileType>(TFileType FileToAdd) where TFileType : File
{
try
{
_session.Save(FileToAdd);
_session.Flush();
}
catch (Exception e)
{
_session.Evict(FileToAdd);
if (e.InnerException.Message.Contains("Violation of UNIQUE KEY"))
throw new ArgumentException("Unique Name must be unique");
else
throw e;
}
}
No difference to the result.
Call _session.Evict(FileToAdd) in the catch block. Although the save fails, FileToAdd is still a transient object in the session and NH will attempt to persist (insert) it the next time the session is flushed.
NHibernate Manual "Best practices" Chapter 22:
This is more of a necessary practice than a "best" practice. When
an exception occurs, roll back the ITransaction and close the ISession.
If you don't, NHibernate can't guarantee that in-memory state
accurately represents persistent state. As a special case of this,
do not use ISession.Load() to determine if an instance with the given
identifier exists on the database; use Get() or a query instead.
How can i force Nhibernate transaction to fail(from the calling code) so i can make sure that the failing behavior is working properly?
I can't modify the source code i just need to make it fail!
example :
public void DoSomething(/*Some parameters*/){
using (var tx = _session.BeginTransaction())
{
try
{
//Do something
tx.Commit();
}
catch (Exception)
{
if (tx != null) tx.Rollback();
throw;
}
} }
Throw an exception.
Throw an exception:
using(var sess = sf.OpenSession())
using(var tx = sess.BeginTransaction())
throw new Exception();
Close the connection:
using(var sess = sf.OpenSession())
using(var tx = sess.BeginTransaction())
sess.Connection.Close();
Rollback the transaction:
using(var sess = sf.OpenSession())
using(var tx = sess.BeginTransaction())
tx.Rollback();
If you have to have the exception occur within the tx.Commit(), then maybe insert/update a record with invalid data (e.g. a string that's too long for the db column).
One idea would be while you're debugging stop at the line immediately before the query execution will be begin and turn off the database then let the code run. However I assume there is a better and more programmatic way to test this.
Set some global variable (I know people had global variables but this is a good use) that the transaction internal code reads and if it sees the variable set throw an exception.
You can write a unit test to verify the expected behaviour of your code.
One way to do it is:
[Test]
public void VerifyExceptionIsThrown()
{
Assert.Throws<NHibernate.HibernateException>(
() =>
{
using (var tx = _session.BeginTransaction())
{
_session.Update(new Entity());
tx.Commit();
}
});
}
An attempt to update a transient entity will throw an exeption.
Alternatively if you use the NHibernateValidator you can explicitly fail a validation you have set, say you entity's Name property should no more than 10 characters long.
If you populate your entity's Name property with a string longer than 10 characters and you try to Save it, the tx.Commit() will throw you an exception.