I'm using NHibernate to insert some data into Table A. I want to update the status in Table B if the Table A transaction fails. How do I check if it has failed?
Below is my current code:
// Add userId to Receiver
Receiver receiver = new Receiver();
receiver.User = User.GetById(Convert.ToInt32(listItem.Value));
receiver.Notification = Notification.GetById(notification.NotificationId);
receiver.Save();
Where do I call the NHibernate Transaction? If it fails where do I call NHibernate Rollback and update the Table B status?
Take a look at the Official NHibernate documentation on Exception handling: http://nhibernate.info/doc/nh/en/index.html#manipulatingdata-exceptions
using ( ISession session = sessionFactory.OpenSession() )
{
using ( ITransaction transaction = session.BeginTransaction() )
{
try
{
// Do your save/update here for Table A
transaction.Commit();
}
catch( Exception e )
{
// Your save or update failed so this is where you
// could capture that info and update your Table B
transaction.Rollback();
}
}
}
From what I remember, you don't actually have to call tx.Rollback() because when your code leaves the using blocks, it will do that automatically but again, I can't remember exactly. Give it a try and if it doesn't behave as I just described, you can manually rollback in the catch.
using (ISession session = factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
// do some work
tx.Commit();
}
or manually
ISession session = factory.openSession();
try
{
// do some work
session.Flush();
currentTransaction.Commit();
}
catch (Exception e)
{
currentTransaction.Rollback();
throw;
}
finally
{
session.Close();
}
Take a look NHibernate transactions for more details
Related
I am trying to improve performance of our nHibernate (3.3.2.4000) application (.NET 4.0). Currently, we are performing CRUD operations one by one, which ends up taking a lot of time, so my plan was to use the ConcurrentQueue and Tasks.
I refactored my code into this:
public void ImportProductsFromXml(string path)
{
List<Product> products = Mapper.GetProducts(path);
var addQueue = new ConcurrentQueue<Product>(productsToAddUpdate);
var updateTasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
var taskId = i + 1;
updateTasks.Add(Task.Factory.StartNew(() => ProcessAddQueue(taskId, products, addQueue)));
}
}
private void ProcessAddQueue(int taskId, List<Product> products, ConcurrentQueue<Product> queue)
{
Product result = null;
while (queue.TryDequeue(out result))
{
try
{
UpdateProducts(products, result);
}
catch (Exception ex)
{
Debug.WriteLine(string.Format("ProcessAddQueue: taskId={0}, SKU={1}, ex={2}", taskId, result.ProductId, ex));
}
}
}
private void UpdateProducts(List<Product> productsFromFile, Product product)
{
...code removed...
CatalogItem parentItem = _catalogRepository.GetByCatalogItemId(category);
...code removed...
_catalogRepository.Save(parentItem);
...code removed...
}
public CatalogItem GetByCatalogItemId(string catalogItemId)
{
using (ISession session = SessionFactory.OpenSession())
{
return session
.CreateCriteria(typeof (CatalogItem))
.Add(Restrictions.Eq("CatalogItemId", catalogItemId))
.List<CatalogItem>().FirstOrDefault();
}
}
The "Save"-method of the catalogRepository calls this method, behind the scenes:
public int Add(T entity)
{
using (ISession session = SessionFactory.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
var id = (int) session.Save(entity);
transaction.Commit();
return id;
}
}
So my idea was to create a concurrentqueue containing all the products, and then process them 5 at a time.
However, I am getting an 'Thread was being aborted exception':
at System.WeakReference.get_Target()
at System.Transactions.Transaction.JitSafeGetContextTransaction(ContextData contextData)
at System.Transactions.Transaction.FastGetTransaction(TransactionScope currentScope, ContextData contextData, Transaction& contextTransaction)
at System.Transactions.Transaction.get_Current()
at NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.EnlistInDistributedTransactionIfNeeded(ISessionImplementor session)
at NHibernate.Impl.SessionImpl.get_PersistenceContext()
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
What am I doing wrong?
Hibernate sessions are meant to be used as unit of work. You open a session, open a transaction on it, load your entity, modify it, call save, commit/rollback the transaction and then dispose the session.
You should be using ONE session to load your entity and then save it. Currently you are loading an entity with one session and saving it with some other session. Combined with concurrent access this could cause problems.
Try loading and saving the entity with the same hibernate session.
When using hibernate as mentioned it should be fully threadsafe. Please note that a single hibernate session is NOT threadsafe.
I am using this piece of code as a part of my auditTrail Class but I am facing a stackOverFlow Exception when I try to log the changes to the database.
public void OnPostInsert(NHibernate.Event.PostInsertEvent #event)
{
if (!(#event.Entity is IAuditable))
{
using (ITransaction transaction = #event.Session.BeginTransaction())
{
if (#event.State != null)
{
for (int i = 0; i < #event.State.Length; i++)
{
string propertyName = #event.Persister.PropertyNames[i];
if (#event.State[i] != null)
{
if (#event.State[i].GetType().Namespace.StartsWith("Averma.Fda.Domain"))
{
CompareIfOldStateIsNull(#event.State[i], Convert.ToInt32(#event.Id), #event.Entity.GetType().ToString(), #event.Session, false);
}
else
{
string auditEntry = "New value for " + SplitPropertyName(propertyName) + " has been added, the new value is " + #event.State[i];
#event.Session.Save(new AuditTrail() { AuditEntry = auditEntry, RelatedEntityId = Convert.ToInt32(#event.Id), RelatedEntityType = #event.Entity.GetType().ToString(), OperationType = Shared.Enums.OperationType.Insert });
}
}
}
}
transaction.Commit();//the error occurs here
}
}
}
Could anyone guide me about how to resolve this issue and how can I log the changes to the database .
Do not begin a new transaction and commit it inside the NHibernate interceptor because a transaction is already open and will be committed after the interceptor finishes its work, all what you want to do is to remove using (ITransaction transaction = #event.Session.BeginTransaction()) and to remove transaction.Commit(); and things will be ok.
The problem is when you save your audit entry, it tries to create another audit log entry for the first audit entry and it repeats. The fix would be to check the target table is audit log table and not to create entry if it's the case.
well my problem is:
I have a method like:
class Manager
{
void method1()
{
// save object in database to get ID
int newId = this.Repository.Save(obj);
try {
// call remote webservice to save another object with same ID as in local DB
webservice.Save(remoteObj, id);
}
catch(Exception e)
{
// do Rollback in Repository here
}
}
}
Bassically this is the code. Repository use NHibernate to save to DB. I need to save in DB to know the new ID and then send this ID to webservice. If something fail calling webservice I want to rollback and discard saved object.... and here is my problem. I can't open and control a transaction in Repository from my class Manager.
I already try with this also:
class Manager
{
void method1()
{
using (TransactionScope scope = new TransactionScope())
{
// save object in database to get ID
int newId = this.Repository.Save(obj);
// call remote webservice to save another object with same ID
// as in local DB
webservice.Save(remoteObj, id);
scope.Complete();
}
}
}
Here the problem is that the rollback is OK but not the Save(Create in NHibernate). I get error about that object "Transaction" is not found or the transaction is already closed just after the line : "scope.Complete();".
I think that something is wrong trying to control NHibernate transaction with TransactionScope .
I dont know if is a problem about approach, maybe another way should be used to handle this situation... ??
any help or idea where to find ??
Thanks a lot !!
Assuming you already have an opened session in a CurrentSession property/variable and that you could pass that working session to your repository, I would do the following:
using(var trx = CurrentSession.BeginTransaction())
{
try
{
int newId = this.Repository.Save(obj, CurrentSession);
webservice.Save(remoteObj, id);
trx.Commit();
}
catch
{
trx.Rollback();
}
}
We have an Enrollment object that has a Student object and the Student object has many Enrollment objects. If I leave off the Cascade.SaveUpdate() from the Enrollment's Student reference, updates to the Student table do not execute, but updates to the Enrollment object succeed. But if I add the Cascade.SaveUpdate() on the Enrollment's Student reference, the updates to the Student table work fine, but updates to the Enrollment table fail. No exceptions are thrown, the updates just don't succeed.
There must be some way to be able to save objects on both sides of the relationship, but what am I missing?
Here's the code snips, let me know if you need more:
EnrollmentMap:
References(x => x.Student)
.Column("student_id");// without the cascade on the next line, this fails to update changes to Student
//.Cascade.SaveUpdate();// when uncommented this updates changes to Student but blocks updates to Enrollment
StudentMap:
HasMany(x => x.Enrollments)
.KeyColumn("student_id")
.Inverse()
.Cascade.SaveUpdate();
Database call:
public Application GetApplication(long applicationId)
{
using (var session = sessionFactory.OpenSession())
{
var query = session.Linq();
query.Expand(x => x.Enrollment);
query.Expand(x => x.Enrollment.Student);
var result = from entity in query
where entity.ApplicationId == applicationId
select entity;
return result.Count() > 0 ? result.First() : null;
}
}
Database save:
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(entity);
transaction.Commit();
}
catch(Exception ex)
{
transaction.Rollback();
throw;
}
}
}
You should try and load your entity in the same session as you update it. I think that is your problem.
If you really can't do this, then it is possible to 'merge' the entity in to your session (google 'NHibernate merge').
I am using Nhibernate in a session-per-request context.
When I use session.Update and commit or rollback my transaction, I get an ObjectDisposedException.
the stack trace for this exception is:
at NHibernate.Transaction.AdoTransaction.CheckNotDisposed()
at NHibernate.Transaction.AdoTransaction.Rollback()
at MyService.update(MyItem item) in C:\Projects\MyProject\ItemService.cs:line 121
at MyController.Edit(Nullable`1 pageId, MyItem item, FormCollection collection) in C:\Projects\MyProject\ItemController.cs:line 251
Before I perform the update the transaction contains the following properties:
isActive: true,
wasCommitted:false,
wasRollBacked: false
After I performed the update action the properties have the following values:
isActive: false,
wasCommitted:true,
wasRollBacked: false
Why do I get the exception and why do the booleans change without committing ?
I am using the following code to perform this action:
using (var tx = SessionManager.CurrentSession.BeginTransaction())
{
try
{
//perform update
wysiwygitemRepository.Update(item);
// perform an action that raises an exception (because of null value)
pageSettingService.SaveSettings(null, item.Id);
tx.Commit();
}
catch(Exception)
{
tx.Rollback();
}
}
I used fluhmode.none as flushmode, but also tried flushmode.auto
Even if I change the code to the following , I still got the same exception:
using (var tx = SessionManager.CurrentSession.BeginTransaction())
{
try
{
//perform update
SessionManager.CurrentSession.Update(item);
tx.Commit();
}
catch(Exception)
{
tx.Rollback();
}
}
I already solved the problem.
I used some nhibernate interceptors which caused the problem.