I have a very ugly problem with a code made by a co worker. The action is within a TransactionScope
. First a database insert is performed:
var billingRecord = new Billing
{
ACCOUNT_ID = AccountId,
AMOUNT = Amount,
};
_ObjectContext.AddToBilling(billingRecord);
_ObjectContext.SaveChanges(SaveOptions.None)
Then a web service call is performed:
var webServiceCallResult = Request(params);
if (webServiceCallResult.Result == 1)
{
success = true;
}
else
{
success = false;
}
If the web service call is ok, the transaction is completed in a finally block:
finally
{
if (success)
{
tran.Complete();
_ObjectContext.AcceptAllChanges();
_Logger.Info(String.Format("Transaction completed"));
}
else
{
_Logger.Info(String.Format("Transaction uncompleted"));
}
}
The problem is that for some reason, some records are not stored in the database. I tested a lot of transactions but that never happen to me in development environment, but sometimes happens in production environment. When I say "sometimes" it's because this situation is very unusual or rare.
Looking in the log file, I can see the message:
Transaction completed
and no exceptions displayed, so the web service call is good and the transaction was completed but the record was not inserted to table.
I know that is not necessary to create a TransactionScope
because there is a only a insert and no additional database operations are needed. The object context of EF is created like a global var in the class and is never disposed , that is a bad practice but as far as I have knowledge the ObjectContext
will be destroyed by the garbage collector, so I think that is not what causes the problem.
I read a lot about transactions and the correct way to use a Entity Framework ObjectContext
and the methods SaveChanges()
and AcceptAllChanges()
and even the code is not using the best practices that should work. I don't want only refactor the code, I would like to know the real reason of the problem.
I would appreciate your help.
I am using:
- ASP.NET MVC 3,
- Entity Framework 5.0,
- Ninject,
- DevExpress
Here is the the complete class:
public class Implementation : IExecute
{
private readonly Logger _Logger;
private readonly ExampleEntities _ObjectContext = new ExampleEntities();
public TopUpExecuteImplementation()
{
_Logger = LogManager.GetLogger("Logger");
}
public Response perfomOperation(String account, String amount)
{
var success = false;
using (var tran = new System.Transactions.TransactionScope())
{
try
{
var accountRecord =
_ObjectContext.Accounts.First(
p => p.Account.Equals(account, StringComparison.InvariantCultureIgnoreCase));
var billingRecord = new Billing
{
ACCOUNT = account,
AMOUNT = amount,
};
_ObjectContext.AddToBillings(billingRecord);
_ObjectContext.SaveChanges(SaveOptions.None);
var webServiceCallResult = Request(account,amount);
_Logger.Info(String.Format("Request Result {0} ", webServiceCallResult.Result));
if (webServiceCallResult.Result == 0)
{
success = false;
}
else
{
if ((String.IsNullOrEmpty(webServiceCallResult.statusCode) == false) &&
(webServiceCallResult.statusCode.Equals("Success",
StringComparison.InvariantCultureIgnoreCase)))
{
success = true;
}
else
{
success = false;
}
}
}
catch (OptimisticConcurrencyException ex)
{
_Logger.Info(String.Format("Exception type {0} Exception {1} Inner Exception {2} ",
ex.GetType().ToString(), ex.Message,
ex.InnerException != null ? ex.InnerException.Message : String.Empty));
_ObjectContext.SaveChanges();
success = true;
}
catch (Exception e)
{
_Logger.Info(String.Format("Exception type {0} Exception {1} Inner Exception {2} ",
e.GetType().ToString(), e.Message,
e.InnerException != null ? e.InnerException.Message : String.Empty));
success = false;
}
finally
{
if (success)
{
tran.Complete();
_ObjectContext.AcceptAllChanges();
_Logger.Info(String.Format("Transaction completed"));
}
else
_Logger.Info(String.Format("Transaction uncompleted"));
}
}
return returnValue;
}
}