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.
Related
I have the following method:
public byte[] HtmlToDoc(string hmtl, string userId)
{
byte[] data;
var auditor = new ServiceAuditor
{
User = userId
};
try
{
using (var tx = new ServerText())
{
tx.Create();
tx.Load(Server.HtmlDecode(hmtl), StringStreamType.HTMLFormat);
tx.Save(out data, BinaryStreamType.MSWord);
}
}
catch (Exception e)
{
auditor.Errormessage = e.Message + "/n " + e.StackTrace;
data = new byte[0];
}
finally
{
auditor.Save();
auditor.Dispose();
}
return data;
}
and I receive the following warning during compilation:
warning CA2000: Microsoft.Reliability : In method
'DocCreator.HtmlToDoc(string, string)', object 'new ServiceAuditor()'
is not disposed along all exception paths. Call
System.IDisposable.Dispose on object 'new ServiceAuditor()' before all
references to it are out of scope.
The weird thing is that I don't see why it is complaining even though I am disposing the object.
Could you point where is the issue?
The issue you have is this line:
auditor.Save();
If that throws an exception, the next line won't run which is responsible for disposing your auditor object. So you could wrap the Save call in another try/catch, but really you should just rely on the using statement to do this for you as that implicitly calls the Dispose method, for example:
public byte[] HtmlToDoc(string hmtl, string userId)
{
byte[] data;
//Add using statement here and wrap it around the rest of the code
using(var auditor = new ServiceAuditor { User = userId })
{
try
{
using (var tx = new ServerText())
{
tx.Create();
tx.Load(Server.HtmlDecode(hmtl), StringStreamType.HTMLFormat);
tx.Save(out data, BinaryStreamType.MSWord);
}
}
catch (Exception e)
{
auditor.Errormessage = e.Message + "/n " + e.StackTrace;
data = new byte[0];
}
finally
{
auditor.Save();
//No need to manually dispose here any more
}
}
return data;
}
Thanks #DavidG for your response, definitely there is a point of error in the line mentioned, but what is causing the warning is the initialization of the object:
//Add using statement here and wrap it around the rest of the code
using(var auditor = new ServiceAuditor { User = userId })
{
try
{ ...
Should be:
using(var auditor = new ServiceAuditor())
{
auditor.User = userId;
try
{ ...
I found the reference for this issue here CA2000: Dispose ...
Initializing members of a disposable object should not be done in the
constructor of a using statement.
I want to check if the connection to Elasticsearch database is ok. In other words I want to ping Elasticsearch. When I execute the code below, an exception is thrown.
public async Task<HealthCheckResult> Execute()
{
if (_configuration.Nodes?.Length == 0)
{
await Task.Delay(1);
return new HealthCheckResult("Connection Failed - Missing elasticsearch connection string")
{
ChildResults = new List<HealthCheckResult>() {new HealthCheckResult()}
};
}
var node = new Uri(_configuration.Nodes.First());
try
{
var connectionPool = new SniffingConnectionPool(new[] {node});
var settings = new ConnectionConfiguration(connectionPool);
var client = new ElasticLowLevelClient(settings);
client.IndicesExists<string>("applications");
}
catch (Exception exception)
{
return new HealthCheckResult(exception.Message)
{
ChildResults = new List<HealthCheckResult>() { new HealthCheckResult() }
};
}
return new HealthCheckResult("Connection Passed")
{
ChildResults = new List<HealthCheckResult>() { new HealthCheckResult() }
};
}
When I execute method above, exception is thrown and I get this message:
Failed sniffing cluster state.
What can I do to check if the connection to Elasticsearch is established?
The Nest IElasticClient interface provides a Ping method for this purpose
I was having the same problem and I managed to fix this by changing the SniffingConnectionPool to a SingleNodeConnectionPool.
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;
}
}
Currently playing around with Dapper I'm trying to insert values into the db as follows
using (var sqlCon = new SqlConnection(Context.ReturnDatabaseConnection()))
{
sqlCon.Open();
try
{
var emailExists = sqlCon.Query<UserProfile>(#"SELECT UserId FROM User_Profile WHERE EmailAddress = #EmailAddress",
new { EmailAddress = userRegister.EmailAddress.Trim() }).FirstOrDefault();
if (emailExists == null) // No profile exists with the email passed in, so insert the new user.
{
userProfile.UniqueId = Guid.NewGuid();
userProfile.Firstname = userRegister.Firstname;
userProfile.Surname = userRegister.Surname;
userProfile.EmailAddress = userRegister.EmailAddress;
userProfile.Username = CreateUsername(userRegister.Firstname);
userProfile.Password = EncryptPassword(userRegister.Password);
userProfile.AcceptedTerms = true;
userProfile.AcceptedTermsDate = System.DateTime.Now;
userProfile.AccountActive = true;
userProfile.CurrentlyOnline = true;
userProfile.ClosedAccountDate = null;
userProfile.JoinedDate = System.DateTime.Now;
userProfile.UserId = SqlMapperExtensions.Insert(sqlCon, userProfile); // Error on this line
Registration.SendWelcomeEmail(userRegister.EmailAddress, userRegister.Firstname); // Send welcome email to new user.
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
sqlCon.Close();
}
}
The error I get is
ExecuteNonQuery requires the command to have a transaction when the connection
assigned to the command is in a pending local transaction. The Transaction
property of the command has not been initialized.
I have googled this error, but I misunderstood the answers provided.
From the error message I assume that you have started a transaction that was neither committed nor rolled back. The real cause for this error message is elsewhere.
I suggest you to log requests in Context.ReturnDatabaseConnection() and trace what requests precede this error.
Also I advice you to look in your code for all transactions and check if they are correctly completed (commit/rollback).
I use following script to get data from external service and store in dB. In certain rare cases less than 1% records gets updated with null values. In below code, the "re.status=fail" we see null. let us know if any thots.
public void ProcessEnquiries()
{
List<req> request = new List<req>();
var options = new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["MaxDegreeOfParallelism"]) };
try
{
Parallel.ForEach(request, options, currentRequest =>
{
ProcessedRequest processedRequest = null;
processedRequest = CommunicateToWS(currentRequest); // Here we call to webservice
});
}
catch (AggregateException exception)
{
foreach (Exception ex in exception.InnerExceptions)
{
// Handle Exception
}
}
}
public ProcessedRequest CommunicateToWS(req objReq)
{
ProcessedRequest re = new ProcessedRequest();
using (WebCall obj = new WebCall())
{
re.no = refnu;
try
{
retval = obj.getValue(inval);
objProxy.Close();
//get data
// parse and store to DB
}
catch (Exception e)
{
re.status = "fail";
//update DB that request has failed
//Handle Exception
obj.Close();
}
}
}