For saving a list of (virtual) bank account transactions, I want the business entity to reflect the state saved to database, also in case of an exception.
Can I assume that an exception here also means the transaction is rolled back? Or can I explicitly rollback in the catch to be sure? If so, what if that line throws an exception?
In Repository< T >:
public void SaveOrUpdate(IList<T> entityList)
{
using (ISession session = FluentNHibernateManager.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
foreach (T entity in entityList)
session.SaveOrUpdate(entity);
transaction.Commit();
}
catch (Exception e)
{
MyTrace.Exception(e.ToString());
// add this line? transaction.Rollback();
throw;
}
}
}
}
In Some Class:
cashTransactions.Add(t);
try {
GenericRepository<CashTransaction> repo = new GenericRepository<CashTransaction>();
repo.SaveOrUpdate(cashTransactions);
} catch (Exception ex) {
cashTransactions.Remove(t);
}
You can't assume that the transaction was rolled back, but you don't have to assume: ITransaction has a bool WasCommitted property.
You can check that to determine whether the transaction was committed, and call Rollback() explicitly, where warranted.
You've gotta include the rollback() call to rollback the transaction properly.
To dispose a not-commited transaction will always rollback it.
This is true in all ado.net transactions implementation, and of course at runtime NHibernate will use your chosen ado.net provider.
Related
I need to save the information about an exception to the database.
i.e
SomeMethod()
{
try
{
using(transaction = context.beginTransaction())
{
try
{
// here is the database error
await transaction.CommitAsync();
}
catch(Exception e)
{
await transaction.Rollback();
throw;
}
}
}
catch(Exception exception)
{
// Here I get the same error that was generated inside the transaction
context.Set<LogEntity>().Add(new LogEntity(....));
await context.SaveChangesAsync();
}
}
Do I understand correctly that a context is a connection to a database and that in one context I can perform several transactions?
As I understand it, the context in this stage is in a dirty state?
How can I write to the database after a transaction error has occurred?
As I understand it, the context in this stage is in a dirty state?
Correct. The "database error" must have occurred in a SaveChanges(Async) call, so if you repeat that call on the same context, the exception will occur again. The remedy is simple: use a new context in the catch block.
Side note: catching the exception in the using block isn't necessary. If the transaction isn't committed before it's disposed, it will be rolled back.
public static void SetStatus( Status statusObject,int retryCount)
{
if (statusObject != null)
{
using (SqliteConnection dbConn = new SqliteConnection(dbURL))
{
IDbTransaction dbTransaction = null;
try
{
dbConn.Open();
dbTransaction = dbConn.BeginTransaction();
new SqliteCommand(some_query, dbConn).ExecuteNonQuery();
}
catch (Exception e)
{
dbTransaction.Rollback();
dbConn.Close();
if (retryCount > 0)
{
SetStatus(statusObject, --retryCount);
return;
}
else
throw e;
}
finally
{
try { dbTransaction.Commit(); }
catch (Exception e)
{
}
}
}
}
}
Whenever ExecuteNonQuery fails due due to some exception I have a retry mechanism which will run the same query again.In that case during the second time(while retrying) the following exception comes-
"Connection must be valid and open to commit transaction"
The problem is basically, that you commit the transaction in the finally block—which runs always per design—even though you may already have rolled back the transaction and closed the connection in the catch block.
The easier way would be to commit the transaction while still in the try block, because after all, you only want to commit the transaction if all the code in the try succeeded.
That being said, you are having some odd nesting with the try and using constructs there. You should try to keep things like usings as concise as possible. That way you will also not have to deal with closing connections yourself. Something like this:
public static void SetStatus(Status statusObject, int retryCount)
{
if (statusObject == null)
return;
try
{
using (var dbConn = new SqliteConnection(dbURL))
{
IDbTransaction dbTransaction = null;
try
{
dbConn.Open();
dbTransaction = dbConn.BeginTransaction();
new SqliteCommand(some_query, dbConn).ExecuteNonQuery();
dbTransaction.Commit();
}
catch
{
// transaction might be null if the connection couldn’t be opened
dbTransaction?.Rollback();
throw;
}
}
}
catch (Exception ex)
{
if (retryCount > 0)
SetStatus(statusObject, --retryCount);
else
throw ex;
}
}
One final note, one could argue that using a transaction is absolutely not necessary here: You are only executing a single query on this database connection, so either that works, or it will fail in which case there is not something to rollback anyway. So unless you are having multiple queries there, you don’t need an explicit transaction (but I’ll just assume that you just reduced your example to show just a single query).
My bad,screwed up using "finally" improperly myself.So if the query fails at first attempt it would go to catch block which will fire the call to re run the query.Second time query execution would be successful and transaction would be committed.Then the control goes to finally of first query which will again try to commit the already rolled back transaction. So,it would throw that exception.
So,if the moving the commit to main try block fixed the issue.
Example: Using transaction, and only committing if a given condition is satisfied.
using (var transaction = context.Database.BeginTransaction())
// ...
if (modelState.IsValid) {
transaction.Commit();
}
}
does it need to be:
using (var transaction = context.Database.BeginTransaction())
// ...
if (modelState.IsValid) {
transaction.Commit();
} else {
transaction.Rollback();
}
}
or there is no need for the else part. What happens if I don't specify?
Any good guidelines?
You don't need to call Rollback manually because you are using the using statement.
It is not necessary to explicitly call Rollback. The transaction will be rolled-back if Commit() has not been called.
DbContextTransaction.Dispose method will be called in the end of the using block. And it will automatically rollback the transaction if the transaction is not successfully committed
What is the right way to handle exception inside nested using statement? I have the following part of code:
public void Create(Entity entity)
{
try
{
using (ISession session = NhibernateHelper.OpenSession())
{
try
{
using (ITransaction transaction = session.BeginTransaction())
{
session.Save(entity);
transaction.Commit();
}
}
catch (TransactionException transactionException)
{
// log it
throw;
}
}
}
catch (TransactionException transactionException)
{
// log it
throw;
}
catch (SessionException sessionException)
{
// log it
throw;
}
catch (Exception exception)
{
// log it
throw;
}
}
I saw few answers when people put try/catch statement inside nested using. I know that using statement consists of try/finally. And my question is about right way to catch all possible exceptions. Should I do rollback inside one of catch statements? Could you provide me the right way to do it?
I tend to minimize "try catch" clauses as much as possible.
Nesting "try catch" and logging each catch (while not swallowing exception) may cause a same exception to be logged multiple times, which bloats the logs. And having "try catch" everywhere bloats the code.
I do not see the need for explicitly rollbacking a failed transaction : non committed transaction are rollbacked on dispose by default.
My usual pattern with MVC/webform is to use a global action filter (usually derived from HandleErrorAttribute) for logging exception, and/or a dedicated IHttpModule. So no need for "try catches" anywhere else just for logging while not swallowing the exception.
(With MVC, I usually explicitly rollback failed transaction because I am using an action filter for opening them OnActionExecuting, committing or rollbacking them OnActionExecuted depending on filterContext state. There, especially if there was an error, I may add a swallowing and logged try catch around the rollback: it may fails too, and I consider this failure should not mask the one having caused the app to try rollbacking.)
I follow the pattern of always beginning a try..catch block immediately after BeginTransaction().
Other than that, I think it's fine to not add any other exception checking - just let it propagate out to the calling code.
using (var session = NhibernateHelper.OpenSession())
{
using(var transaction = session.BeginTransaction())
{
try
{
//
// Add a block of code here which queries and
// modifies data
//
//
transaction.Commit();
}
catch(Exception ex)
{
transaction.RollBack();
throw;
}
}
}
I have an connection to LocalDB database:
Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-mysite-20160510105433.mdf
I used code first with Entity Framework and the following piece of code:
var tran = this.context.Database.BeginTransaction();
//some operations on dbcontext
tran.Rollback();
where context variable is simple DbContext instance.
THe exception "The underlying provider failed on rollback" is thrown.
Such an exception is not thrown when using normal, SQL Server connection.
Does it mean that localdb doesn't support transaction? If yes, how to achieve it?
Or does it come from that connections run per process, so when query is over then connection is closed automatically?
Edit: sample operations that I perform:
var myEntity = new MyEntityA();
this.context.MyEntitiesA.Add(myEnttiy).
this.context.Save(); //save to retreive id
// some playing with id
var mySecondEntity= new MyEntityB(){ MyEntityAId = myEntityA.Id, //other data gethered in "playing part" } ;
this.context.MyEntitiiesB.Add(mySecondEntity).
this.context.Save(); //need to rollback first, when here fails.
So I want to enclose within transaction:
using (var tran = this.context.Database.BeginTransaction())
{
try
{
// operations
tran.Commit();
}
catch (Exception ex)
{
// stuff
tran.Rollback();
}
}
So as you can see transaction is useful here.