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;
}
}
Related
Is this a good way to set the transaction level to serializable? If I set a lower transaction level, I will get duplicate orders. What is better approach? I assume this method will be invoked frequently by different users.
Here is my code
using (var tran = context.Database.BeginTransaction(IsolationLevel.Serializable))
{
try
{
var lastDocument = context.Documents.OrderByDescending(x => x.Id).FirstOrDefault();
int order = 1;
if (lastDocument != null)
{
order = lastDocument.Order + 1;
}
var document = new Document
{
CreatedDate = DateTimeOffset.UtcNow,
Name = Guid.NewGuid().ToString(),
Order = order
};
context.Documents.Add(document);
context.SaveChanges();
tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
}
}
I'm stuck with a problem. I need help...
In general, I have an ASP.NET MVC 5 project. When a user clicks on "Save" button, I run some code in a new created task. I need to know the result of operation, so I return the instance of my class ChangesMade. Then I serialze the object to JSON format and pass to a view. Then I check if result is true, I open an url in a new window.
So, in my controller I have the following:
public async Task<ActionResult> Save(here some parameters)
{
var changes = await _model.SaveAsync(some parameters);
return NewtownJson(changes);
}
The main saving logic is the following:
public async Task<ChangesMade> SaveAsync(some parameters here)
{
var data = (await _model.GetData(some parameter)).ToList();
// create a task of ChangesMade that contains public bool property MemoAdded
// that I need to pass to a view to know the result of operation
var task = Task<ChangesMade>.Factory.StartNew(() =>
{
ChangesMade changes = new ChangesMade();
try
{
using (var tr = new TransactionScope())
{
// some code here omitted for simplicity…
// if (someCondition == true) changes.MemoAdded = true;
tr.Complete();
}
return changes;
}
catch (Exception ex)
{
throw ex;
}
});
try
{
task.Wait();
}
catch (AggregateException ex)
{
string msg = "";
msg= ex.Flatten().InnerExceptions
.Where(e => e != null)
.Select(e => e.Message)
.Aggregate(msg, (current, message) => current + " " + message + ";")
.TrimEnd(';');
throw new Exception(msg);
}
return task.Result;
}
I publish the project on two sites on IIS. The first works fine. But the second doesn't - by some reason, it always returns changes.MemoAdded false to the view.
I can't find out a reason of that. I don't have a clue what to do ...
I have a code that adds data to two EntityFramework 6 DataContexts, like this:
using(var scope = new TransactionScope())
{
using(var requestsCtx = new RequestsContext())
{
using(var logsCtx = new LogsContext())
{
var req = new Request { Id = 1, Value = 2 };
requestsCtx.Requests.Add(req);
var log = new LogEntry { RequestId = 1, State = "OK" };
logsCtx.Logs.Add(log);
try
{
requestsCtx.SaveChanges();
}
catch(Exception ex)
{
log.State = "Error: " + ex.Message;
}
logsCtx.SaveChanges();
}
}
}
There is an insert trigger in Requests table that rejects some values using RAISEERROR. This situation is normal and should be handled by the try-catch block where the SaveChanges method is invoked. If the second SaveChanges method fails, however, the changes to both DataContexts must be reverted entirely - hence the transaction scope.
Here goes the error: when requestsCtx.SaveChanges() throws a exception, the whole Transaction.Current has its state set to Aborted and the latter logsCtx.SaveChanges() fails with the following:
TransactionException:
The operation is not valid for the state of the transaction.
Why is this happening and how do tell EF that the first exception is not critical?
Really not sure if this will work, but it might be worth trying.
private void SaveChanges()
{
using(var scope = new TransactionScope())
{
var log = CreateRequest();
bool saveLogSuccess = CreateLogEntry(log);
if (saveLogSuccess)
{
scope.Complete();
}
}
}
private LogEntry CreateRequest()
{
var req = new Request { Id = 1, Value = 2 };
var log = new LogEntry { RequestId = 1, State = "OK" };
using(var requestsCtx = new RequestsContext())
{
requestsCtx.Requests.Add(req);
try
{
requestsCtx.SaveChanges();
}
catch(Exception ex)
{
log.State = "Error: " + ex.Message;
}
finally
{
return log;
}
}
}
private bool CreateLogEntry(LogEntry log)
{
using(var logsCtx = new LogsContext())
{
try
{
logsCtx.Logs.Add(log);
logsCtx.SaveChanges();
}
catch (Exception)
{
return false;
}
return true;
}
}
from the documentation on transactionscope: http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28v=vs.110%29.aspx
If no exception occurs within the transaction scope (that is, between
the initialization of the TransactionScope object and the calling of
its Dispose method), then the transaction in which the scope
participates is allowed to proceed. If an exception does occur within
the transaction scope, the transaction in which it participates will
be rolled back.
Basically as soon as an exception is encountered, the transaction is rolled back (as it seems you're aware) - I think this might work but am really not sure and can't test to confirm. It seems like this goes against the intended use of transaction scope, and I'm not familiar enough with exception handling/bubbling, but maybe it will help! :)
I think I finally figured it out. The trick was to use an isolated transaction for the first SaveChanges:
using(var requestsCtx = new RequestsContext())
using(var logsCtx = new LogsContext())
{
var req = new Request { Id = 1, Value = 2 };
requestsCtx.Requests.Add(req);
var log = new LogEntry { RequestId = 1, State = "OK" };
logsCtx.Logs.Add(log);
using(var outerScope = new TransactionScope())
{
using(var innerScope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
try
{
requestsCtx.SaveChanges();
innerScope.Complete();
}
catch(Exception ex)
{
log.State = "Error: " + ex.Message;
}
}
logsCtx.SaveChanges();
outerScope.Complete();
}
}
Warning: most of the articles about RequiresNew mode discourage using it due to performance reasons. It works perfectly for my scenario, however if there are any side effects that I'm unaware of, please let me know.
this method - doDayBegin(item.BranchId) is taking long time to execute. so i am using Parallel.ForEach to execute it parallel. when i am using normal foreach loop its working fine but when i am using Parallel.ForEach it showing this error
The context cannot be used while the model is being created Parallel.ForEach
public ActionResult Edit([DataSourceRequest] DataSourceRequest request)
{
try
{
JavaScriptSerializer js = new JavaScriptSerializer();
List<DB0010020Vm> _listDB0010020Vm = new List<DB0010020Vm>();
string dataDB0010020vm = Request.Form["griddetailsvm"];
if (!string.IsNullOrEmpty(dataDB0010020vm))
{
_listDB0010020Vm = js.Deserialize<List<DB0010020Vm>>(dataDB0010020vm).
Where(d => d.IsValid == "YES").ToList();
}
DateTime start = DateTime.UtcNow;
Parallel.ForEach(_listDB0010020Vm, item =>
{
doDayBegin(item.BranchId);
});
DateTime end = DateTime.UtcNow;
TimeSpan duration = end - start;
return Json(new
{
success = true,
message = "Day Begin Process Completed Successfully!" + duration
});
}
catch (Exception e)
{
return Json(new
{
success = false,
message = e.Message
});
}
}
public void doDayBegin(int BranchId)
{
using (var dbThread = new NeoSampleDBEntities()) // new db connection
{
EBS.DAL.Model.DB0010020 branchDetails = _idDB0010020Repository.FindOne(d => d.BranchId == BranchId);
if (branchDetails == null)
{
ModelState.AddModelError("", "Branch not found!");
}
else
{
branchDetails.LastOpenDate = Convert.ToDateTime(Request.Form["LastOpenDate"]);
OperationStatus status = _idDB0010020Repository.UpdateAndSave(branchDetails);
if (status != null && !status.Status)
ModelState.AddModelError("Updation failed", status.ExceptionMessage);
}
EBS.DAL.Model.DB0010044 dayBegin = new DB0010044();
dayBegin.BankId = 1;
dayBegin.BranchId = BranchId;
dayBegin.DayBeginFlag = 1;
dayBegin.DayDate = Convert.ToDateTime(Request.Form["LastOpenDate"]);
dayBegin.DayEndFlag = 0;
dayBegin.DayEndStage = 1;
dayBegin.DayReopenFlag = 0;
OperationStatus status2 = _idDB0010044Repository.AddAndSave(dayBegin);
if (status2 != null && !status2.Status)
ModelState.AddModelError("Updation failed", status2.ExceptionMessage);
else
{
CreateInwardSessionsForBranch(BranchId);
CreateOutwardSessionsForBranch(BranchId);
}
}
}
this is error
what will be the issue?
You create a new instance of the NeoSampleDBEntities DbContext in the doDayBegin method, but you use an existing (and as far as I can tell, single) instance of the repository _idDB0010020Repository, presumably encapsulating its own instance of DbContext or a pre-existing DbContext instance that was passed in on construction.
The error you're seeing is the result of a second thread's call to <repo>.FindOne making a DbContext method call while the model is being generated as a result of the first thread's call to <repo>.FindOne. Even if the model creation finished, you'll more than likely run into conflicts as DbContext is not thread safe.
From what you've posed, my suggestion would be to create a new repo in the using statement of the doDayBegin method instead of a new instance of the NeoSampleDBEntities DbContext.
My guess it's that _listDB0010020Vm has not been calculated yet when the parallel.foreach is called. If you step through the code in a standard non parallel foreach, it would show this by skipping the .where selection line, going into the foreach, then when it needs the list value, step next will show it jump back to the where selector. I believe List.ToArray will force it to calculate. Try paying that result to the parallel loop.
I've code like that:
class Importer
{
private DatabaseContext m_context;
public: Importer()
{
m_context = new DatabaseContext();
m_context.CommandTimeout = 5400; //This is seconds
}
public bool Import (ref String p_outErrorMsg)
{
List<SomeData> dataToImport = new List<SomeData>();
getSomeData(ref dataTiImport);
bool result = false;
try
{
using(TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(2, 0, 0)))
{ //Two hours timeout
result = importDatas(dataToImport);
if (result == true)
{
scope.Complete();
}
}
}
catch (TransactionAbortedException ex)
{
p_outErrorMsg = String.Format("TransactionAbortedException Message: {0}", ex.Message);
}
catch (ApplicationException ex)
{
p_outErrorMsg = String.Format("ApplicationException Message: {0}", ex.Message);
}
}
bool importDatas(List<SomeData> p_DataToImport)
{
foreach (SomeData data in p_DataToImport)
{ //There can be somehitg about 3000 iterations
if (!importSimpleData(data))
{
return false;
}
return true;
}
}
bool importSimpleData(SomeData p_Data)
{
//creation some object o1
try
{
m_context.objetc1s.InsertOnSubmit(o1);
m_context.SubmitChanges();
}
catch (Exception e)
{
//Error handlig
return false
}
//creation some object o2
o2.id_o1 = o1.id_o1;
try
{
m_context.objetc2s.InsertOnSubmit(o2);
m_context.SubmitChanges();
}
catch (Exception e)
{
//Error handlig
return false
}
//creation some object o3
o3.id_o2 = o2.id_o2;
try
{
m_context.objetc3s.InsertOnSubmit(o3);
m_context.SubmitChanges();
}
catch (Exception e)
{
//Error handlig
return false
}
//creation some object o4
o4.id_o1 = o1.id_o1;
try
{
m_context.objetc4s.InsertOnSubmit(o4);
m_context.SubmitChanges();
}
catch (Exception e)
{
//Error handlig
return false
}
return true;
}
}
And if List has 500 records, all is writing fine.
But when the list is near to 1000, I've always exception:
TransactionAbortedException.Message = "the transaction has aborted".
Firstly I think that timeout was to small so I did introduce to code this two lines:
...
m_context.CommandTimeout = 5400; //This is seconds (1.5 hour)
...
using(TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(2, 0, 0))) { //Two hours timeout
...
As you can see in presented above code.
The same exception still occurs, did I miss something?
What do I do wrongly?
I have to add that data base is remote (not local)
Thanks in advance for the help!
I'd have to dig up the documentation again, but setting a transaction timeout to 2 hours may not be happening for you. There is a cap on how long the transaction timeout can be that comes down through machine.config and if you specify more than that cap, it quietly ignores you.
I ran into this a long time ago, and found a reflection-based way to tweak that setting here by Matt Honeycutt to make sure you're really getting the timeout you specify.
It seems that importSimpleData fails on some row and importData returns false. In such case you don't call scope.Complete() and it's the reason why transaction rolls back.