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.
Related
I have a database named _context and I would like to Insert some "worker" in a table named "workers". The function which help me to achieve that, is running every two hours using the background services library. I need to keep an history of what happened when an error occured, so I set up a table named "Log" which would be fill on each error or success.
So I try :
IDbContextTransaction transac = null;
try
{
//...
//code here with _context.workers.toListAsync() etc..
//...
foreach (var worker in WorkerList)
{
transac = _context.Database.BeginTransaction();
_context.workers.Add(worker);
await _context.SaveChangesAsync();
transac.Commit();
//LogSuccess
Log logSuccess = "Worker successfully added";
_context.Log.Add(logSuccess);
await _context.SaveChangesAsync();
}
}
catch(Exception ex)
{
transac.Rollback();
//LogErreur
Log logError = ex.message;
transac = _context.Database.BeginTransaction();
_context.Log.Add(logError);
await _context.SaveChangesAsync();
}
My problem is the following :
When an error occured on the _context.SaveChangesAsync() (right after the worker is added), it goes in the catch and begin another transaction. But when the _context.SaveChangesAsync comes, the program throw the same error that the first _context.SaveChangesAsync() threw just before. For me, the transac.rollback() should "erase" the previous transaction.
A failure in SaveChanges doesn't clear the Change Tracker, so a subsequent call to SaveChanges will try to save all the changes again. This enables you to retry on transient errors or to modify the pending changes before trying again.
Either use a new DbContext or call ChangeTracker.Clear.
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;
}
}
}
In my service I have a background thread that does a best effort saving of a stream of object of certain entity type. Code roughly is following:
while (AllowRun)
{
try
{
using (DbContext context = GetNewDbContext())
{
while (AllowRun && context.GetConnection().State == ConnectionState.Open)
{
TEntity entity = null;
try
{
while (pendingLogs.Count > 0)
{
lock (pendingLogs)
{
entity = null;
if (pendingLogs.Count > 0)
{
entity = pendingLogs[0];
pendingLogs.RemoveAt(0);
}
}
if (entity != null)
{
context.Entities.Add(entity);
}
}
context.SaveChanges();
}
catch (Exception e)
{
// (1)
// Log exception and continue execution
}
}
}
}
catch (Exception e)
{
// Log context initialization failure and continue execution
}
}
(this is mostly the actual code, I omitted few non-relevant parts that attempt to keep popped objects in memory until we are able to save stuff to DB again when exception is caught at (1) block)
So, essentially, there is an endless loop, trying to read items from some list and save them to Db. If we detect that connection to DB failed for some reason, it just attempts to reopen it and continue. The issue is that sometimes (I failed to figure out how to reproduce it so far), the code above when context.SaveChanges() is called starts to produce following exception (caught in (1) block):
System.Data.EntityException: An error occurred while starting a transaction on the provider connection. See the inner exception for details. --->
System.InvalidOperationException: The requested operation cannot be completed because the connection has been broken.
The error is logged, but when the execution returns to the context.GetConnection().State == ConnectionState.Open check, it evaluates to true. So we are in a state when context reports that its DB connection is open, but we can't run queries against that context. Restarting the service removes the issue (as well as messing with AllowRun variable in debugger to force recreation of context). So the question is since I can't trust context's connection state, how do I verify that I can run queries against DB?
Also, is there a clean way to figure out that connection is not in a "healthy" state? I mean, the EntityException by itself is not an indication that I should reset the connection, only if its InnerException is InvalidOperationException with some specific Message, then yes, it is time to reset it. But, now I guess there would be other situations when ConnectionState indicates that everything is fine, but I can't query DB. Can I catch those proactively, not waiting until it starts to bite me?
What is the log frequency?
if this loop take longer than connection timeout, connection closed when savechanges executing.
while (pendingLogs.Count > 0)
{
lock (pendingLogs)
{
entity = null;
if (pendingLogs.Count > 0)
{
entity = pendingLogs[0];
pendingLogs.RemoveAt(0);
}
}
if (entity != null)
{
context.Entities.Add(entity);
}
}
context.SaveChanges();
From my experience working on similar services, garbage collection won't occur until the end of the using block.
If there were a lot of Pending logs to write, this could use a lot of memory, but I also guess it might starve the dbConnection pool.
You can analyse memory usage using RedGate ANTS or a similar tool, and check dbConnections that are open using the following script from this StackOverflow question: how to see active SQL Server connections?
SELECT
DB_NAME(dbid) as DBName,
COUNT(dbid) as NumberOfConnections,
loginame as LoginName
FROM
sys.sysprocesses
WHERE
dbid > 0
GROUP BY
dbid, loginame
;
I think it's good practice to free up the context as often as you can in order to give GC a change of cleaning up, so you could rewrite the loop as:
while (AllowRun)
{
try
{
while (pendingLogs.Count > 0)
{
using (DbContext context = GetNewDbContext())
{
while (AllowRun && context.GetConnection().State == ConnectionState.Open)
{
TEntity entity = null;
try
{
lock (pendingLogs)
{
entity = null;
if (pendingLogs.Count > 0)
{
entity = pendingLogs[0];
pendingLogs.RemoveAt(0);
}
}
if (entity != null)
{
context.Entities.Add(entity);
context.SaveChanges();
}
}
catch (Exception e)
{
// (1)
// Log exception and continue execution
}
}
}
}
}
catch (Exception e)
{
// Log context initialization failure and continue execution
}
}
I recommend go through below url:
Timeout Expired is usually thrown when a sql query takes too long to run.
Sounds like a SQL job is running, backup? That might be locking tables or restarting the service.
ADONET async execution - connection broken error
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.
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.