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.
Related
I'm running the code below to purposely throw JsonReaderException. It correctly gives the exception message of "Could not convert string to boolean: aaa. Path 'Active', line 3, position 17."
Is there any way to get the value that has failed the validation directly from the JsonReaderException so I don't have to parse the exception message?
string json = #"{
'Email': 'james#example.com',
'Active': 'aaa',
'CreatedDate': '2013-01-20T00:00:00Z',
'Roles': [
'User',
'Admin'
]
}";
try
{
Account account = JsonConvert.DeserializeObject<Account>(json);
Console.WriteLine(account.Email);
}
catch (JsonReaderException exc)
{
// Do Something
}
It appears that the offending value is not saved as a property in JsonReaderException. The only possible location for this value would be the Exception.Data dictionary, however Json.NET does not add anything here.
However, with some work you can leverage Json.NET's serialization error event handling functionality to directly access the bad value at the time the exception is thrown. First, define the following helper method and ErrorEventArgs subtype:
public class ErrorAndValueEventArgs : Newtonsoft.Json.Serialization.ErrorEventArgs
{
public object ReaderValue { get; } = null;
public ErrorAndValueEventArgs(object readerValue, object currentObject, ErrorContext errorContext) : base(currentObject, errorContext)
{
this.ReaderValue = readerValue;
}
}
public static partial class JsonExtensions
{
public static TRootObject Deserialize<TRootObject>(string json, EventHandler<ErrorAndValueEventArgs> error, JsonSerializerSettings settings = null)
{
using (var sr = new StringReader(json))
using (var jsonReader = new JsonTextReader(sr))
{
var serializer = JsonSerializer.CreateDefault(settings);
serializer.Error += (o, e) => error(o, new ErrorAndValueEventArgs(jsonReader.Value, e.CurrentObject, e.ErrorContext));
return serializer.Deserialize<TRootObject>(jsonReader);
}
}
}
Now you will be able to access the value of JsonReader.Value at the time the exception was thrown:
object errorValue = null;
try
{
Account account = JsonExtensions.Deserialize<Account>(json, (o, e) => errorValue = e.ReaderValue);
Console.WriteLine(account.Email);
}
catch (JsonException exc)
{
// Do Something
Console.WriteLine("Value at time of {0} = {1}, Data.Count = {2}.", exc.GetType().Name, errorValue, exc.Data.Count);
// Prints Value at time of JsonReaderException = aaa, Data.Count = 0.
}
Notes:
Since you must manually create your own JsonTextReader, you will need to have access to the JSON string (or Stream) for this approach to work. (This is true in the example shown in your question.)
A similar technique for capturing additional error information is shown in JsonSerializationException Parsing.
You might want to enhance ErrorAndValueEventArgs to also record JsonReader.TokenType. In cases where the reader is positioned at the beginning of a container (object or array) at the time an exception is thrown, JsonReader.Value will be null.
Demo fiddle here.
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.
I know, how to use transactions in pure DAO or in ActiveModel, where transaction is initiated before call to $model->save() and rolled back upon any exception.
But how to use transactions, if the only place of code I have access to (no matter, why) is Yii event?
public function beforeDelete()
{
foreach($this->menuItems as $menuItem) $menuItem->delete();
return parent::beforeDelete();
}
If I initiate transaction there, capture possible exception and rollback entire transaction upon it, then only deletion of relational models (here: menu items) will be rolled back. It will not prevent (roll back) deletion of master record.
Does preventing deletion of master record, by returning FALSE in my own beforeDelete in case of exception, is all I need to take care here? Or should I avoid transactions at all in Yii events?
What about override save method:
public function save($runValidation=true,$attributes=null)
{
$transaction=$this->getDbConnection()->beginTransaction();
try
{
$result = parent::save($runValidation,$attributes);
if($result)
$transaction->commit();
else
$transaction->rollback();
}
catch(Exception $e)
{
$transaction->rollback();
$result = false;
}
return $result;
}
Answering my own question with example piece of code to further extend my comment given to Alex's answer:
public function beforeDelete()
{
$transaction = $this->getDbConnection()->beginTransaction();
try
{
foreach($this->menuItems as $menuItem) $menuItem->delete();
$transaction->commit();
return parent::beforeDelete();
}
catch(Exception $ex)
{
$transaction->rollback();
return FALSE;
}
}
Both answers seems correct, both are alternative to each other. Though, I accept Alex answer, as better.
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
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();
}
}