Transaction already exists error - devart

I'm using EF Core and Devart's data provider library. I've hit an issue I can't figure out with handling user input errors smoothly. The error seems to be limited to adding a new entity to the context.
Scenario
User inputs an invalid value in a field.
Save changes is called and throws then displays error.
Prompt user to fix the error.
After this if the error is fixed and save is called again (this is good data now), I get an exception "Transaction already exists" from the Devart data provider library.
StackTrace
at Devart.Data.Oracle.OracleConnection.BeginTransaction(IsolationLevel il)
at Devart.Data.Oracle.OracleConnection.BeginDbTransaction(IsolationLevel isolationLevel)
at System.Data.Common.DbConnection.BeginTransaction(IsolationLevel isolationLevel)
at  .BeginDbTransaction(IsolationLevel )
at System.Data.Common.DbConnection.BeginTransaction(IsolationLevel isolationLevel)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransactionWithNoPreconditions(IsolationLevel isolationLevel)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransaction(IsolationLevel isolationLevel)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransaction()
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.BeginTransaction()
at
I tried to break out the transaction and handle it manually MSDN Transactions but I still get the same error.
public bool SaveAllChanges()
{
var result = false;
using (var transaction = _context.Database.BeginTransaction())
{
try
{
_context.Database.AutoTransactionsEnabled = false;
_context.SaveChanges(true);
transaction.Commit();
result = true;
}
catch (Exception exc)
{
InvokeError(exc, "Error saving changes.");
result = false;
}
}
_context.Database.AutoTransactionsEnabled = true;
_context.Database.CloseConnection();
return result;
}
How do I recover from a db error without scrapping all of the user's input? I would hate for that to be practice. I could be validating all the data going in but recovering from simple errors would be better.

After fussing around with this I found the magic sauce. This type of error only seems to come up when adding an object to the DB. It's as if the context doesn't dispose of the transaction on fail.
public bool SaveAllChanges()
{
var result = false;
_context.Database.AutoTransactionsEnabled = false;
using (var transaction = _context.Database.BeginTransaction())
{
try
{
_context.SaveChanges(true);
transaction.Commit();
result = true;
}
catch (Exception exc)
{
transaction.Rollback(); <-------- Here.
InvokeError(exc, "Error saving changes.");
result = false;
}
}
_context.Database.AutoTransactionsEnabled = true;
_context.Database.CloseConnection();
return result;
}
If someone has a solution to where I don't need to handle the transaction in this way please post it.

We cannot reproduce the "Transaction already exists" exception with the following code:
using (var _context = new MyContext())
{
var entity = new MyEntity() { ID = 10, Name = "entry exceeds max length of the field" };
_context.MyEntities.Add(entity);
try
{
_context.SaveChanges(true); // error
}
catch (Exception ex)
{
//InvokeError(exc, "Error saving changes.");
}
entity.Name = "correct input";
_context.SaveChanges(); // success
}
Please localize the issue in a small application and send us this project for reproducing.

Related

Revert DbContext.Savechanges in case a second DbContext.Savechanges fail

I have the following code, which stores information in two different tables in the same method
public static async Task<Response> AddStockTransaction(StockTransactionsHeader header, List<StockTransactionsDetails> details)
{
using (DataContext dbContext = new DataContext())
{
try
{
dbContext.StockTransactionsHeader.Add(header);
await dbContext.SaveChangesAsync();
int hearderID = header.TransactionHeaderID;
foreach (var item in details)
{
item.TransactionHeaderID = hearderID;
}
dbContext.StockTransactionsDetails.AddRange(details);
await dbContext.SaveChangesAsync();
return new Response
{
IsSuccess = true
};
}
catch (Exception ex)
{
return new Response
{
IsSuccess = false,
Message = ex.Message
};
}
}
}
How can I do, in case there is an exception in the second SaveChanges () to revert the first one?
Once SaveChanges has been called, your datat is stored on your database. You should not call SaveChanges more than once in a call, unless you are willingly to persist the intermediate steps.
You can use a transaction scope to create managed transactions :
using (TransactionScope scope = CreateTransactionScope())
{
DoSomthing(context);
scope.Complete();
}
however, if the failure of the second part involves rolling back the first one, this means that both parts belong to the same transaction, therefore simply omitting the first SaveChanges would turn your code into a single transaction.
From my another awnser: You could use DbTransaction class.
private void TestTransaction()
{
var context = new MyContext(connectionString);
using (var transaction = context.Database.BeginTransaction())
{
try
{
// do your stuff
// commit changes
transaction.Commit();
}
catch
{
// 'undo' all changes
transaction.Rollback();
}
}
}

Vert.x: Blocking handler issue

I want to use blocking handler, but still get an error:
java.lang.IllegalStateException: Response has already been written
Here is my code:
Server.java
r.route("/api/details/send/").handler(BodyHandler.create());
r.route("/api/details/send/").handler(ctx-> {
JsonArray ja = ctx.getBodyAsJsonArray();
JsonArray params = new JsonArray();
vertx.executeBlocking(futur -> {
for(int i =0; i<ja.size();i++) {
JsonObject req = new JsonObject();
req.put("QUERY", "INSERT INTO detailsfacture VALUES ('',?,?,?,?,?,?,?)");
req.put("DB", "MYSQL_");
params.add(ja.getJsonObject(i).getValue("typefacture"))
.add(ja.getJsonObject(i).getValue("activites"))
.add(Integer.parseInt(ja.getJsonObject(i).getValue("qte").toString()))
.add(Double.parseDouble(ja.getJsonObject(i).getValue("pu").toString())
.add(ja.getJsonObject(i).getValue("unite"))
.add(Double.parseDouble(ja.getJsonObject(i).getValue("montant").toString())
.add(ja.getJsonObject(i).getValue("codefacture"));
req.put("PARAMS", params);
eb.send("EXECUTE", req, res -> {
if (res.succeeded()) {
params.clear();
ctx.response().putHeader("content-type", "application/json").end(res.result().body().toString());
} else {
ctx.response().putHeader("content-type", "application/json").end(res.cause().getMessage());
}
});
}
String result = "orsys";
futur.complete(result);
},resultat->{
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, "text/plain");
//resultat.result().toString();
});
});
MySql.java
eb.consumer("MYSQL_EXECUTE_WITH_PARAMS", req->{
try{
JsonObject reqParams = (JsonObject)req.body();
String sql = reqParams.getString("QUERY");
client.getConnection( connection -> {
if (connection.succeeded()) {
try{
SQLConnection con = connection.result();
con.updateWithParams(sql,reqParams.getJsonArray("PARAMS"), query -> {
if(query.succeeded()){
UpdateResult urs = query.result();
req.reply(urs.toJson());
//req.reply(query.result());
}else{
req.fail(24, "Err Request : "+query.cause().getMessage());
}
});
}catch(Exception e){
req.fail(24, "Err Conn Failed : "+e.getMessage());
}
} else {
req.fail(24, "Err No Connection : "+connection.cause().getMessage());
}
});
}catch(Exception e){
req.fail(24, e.getMessage());
}
});
P.S. : When I remove executeBlocking only the first records is registred in my database.
Regards.
You insert entities into detailsfacture in a loop. For each insert you call following:
ctx.response().putHeader("content-type", "application/json").end(res.result().body().toString());
As you can see you call the end(...) method of the response object. Thats where the IllegalStateException comes from. As the documentation states:
Once the response has ended, it cannot be used any more.
So you problem has nothing to do with the executeBlocking.
You should take a look at the write(...) method of HttpServerResponse. For each insert you should call write(...) instead of end(...). But this will only work if you know the complete length of the whole response because you need to set the header Content-length. If you are finished with all inserts you need to call end() to complete the response. Also you should only set the header once and not for each insert.
Now some additional comments. I don't see the need for executeBlocking in your case. Because of the problem with Content-length I recommend to wrap each insert with a Future and compose all of them with CompositeFuture. The Future futur is used the wrong way. The send(...) method of Event bus is not blocking and asynchronous. So the futur.complete(result) is called right after you send all your inserts. Also it's strange that the consumer consumes MYSQL_EXECUTE_WITH_PARAMS and the send sends to EXECUTE.
I tried another solution to get my query like that (?,?,...,?),(?,?,...,?),..,(?,?,...,?).
Here is my code :
public static String getMultipleInsertReq(String table, JsonArray columns,JsonArray data){
JsonObject tab= Tables.Tables_list.getJsonObject(table); // name of table
String sql = "";
if(tab != null){
sql = "INSERT INTO "+table + "( ";
if(columns == null){
columns = tab.getJsonArray("COLS"); //columns from ur database
}
if(columns!=null){
for(int i=0;i<columns.size();i++){
if(i==columns.size()-1){
sql+=columns.getString(i)+") VALUES";
}
else{
sql+=columns.getString(i)+",";
}
}
for(int i =0; i<data.size();i++){
for(int j=0; j<columns.size();j++){
if(j==columns.size()-1 && i!=data.size()-1){
sql+="?),";
}
else if (i==data.size()-1 && j==columns.size()-1){
sql+="?)";
}
else if (j==0){
sql+="(?,";
}
else{
sql+="?,";
}
}
}
return sql;
}
}
return null;
}
Hope it helps.
P.S.: it's only a query builder so you can adapt it depending on your needs.
Regards.

How to ignore a DbUpdateConcurrencyException when deleting an entity

I have an app that reads a lot of data into memory and processes it in a batches.
What I want is for entity framework to ignore DbUpdateConcurrencyException when deleting an entity that has already been deleted.
The reason is that by the time an entity has been processed and marked for deletion, it may already have been deleted from the DB.
Obliviously deleting a row that has already been deleted isn't a problem and shouldn't cause an error, I just need a way to tell entity framework that :)
Example
Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();
Causes an error if itemToRemove has already been deleted.
Note: Db.Configuration.ValidateOnSaveEnabled = false; doesn't fix this as another thread suggested.
How about?
Db.Entry(itemToRemove).State = EntityState.Deleted;
bool saveFailed;
do
{
saveFailed = false;
try
{
Db.SaveChanges();
}
catch(DbUpdateConcurrencyException ex)
{
saveFailed = true;
var entry = ex.Entries.Single();
//The MSDN examples use Single so I think there will be only one
//but if you prefer - do it for all entries
//foreach(var entry in ex.Entries)
//{
if(entry.State == EntityState.Deleted)
//When EF deletes an item its state is set to Detached
//http://msdn.microsoft.com/en-us/data/jj592676.aspx
entry.State = EntityState.Detached;
else
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
//throw; //You may prefer not to resolve when updating
//}
}
} while (saveFailed);
More here:
Resolving optimistic concurrency exceptions
I posted this question a long time ago but it has recently had some attention so I though I would add the solution I actually use.
//retry up to 5 times
for (var retries = 0; retries < 5; retries++)
{
try
{
Db.SaveChanges();
break;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entity in ex.Entries)
{
entity.State = EntityState.Detached;
}
}
}
Things I considered - I did NOT want to use ReloadAsync() or ObjectContext.Refresh as I wanted to ignore items deleted in another process WITHOUT any additional database overhead.
I added in the for loop as a simple protection against infinite loops - not something that should be able to happen, but I'm a belt and braces approach man and not a fan of while(true) if it can be avoided.
No need to a local variable like isDone or saveFailed - simply break if we saved successfully.
No need to cast ex.Entries to a list in order to enumerate it - just because you can write something on one line doesn't make it better.
You could handle the DbUpdateConcurrencyException and then call Refresh(RefreshMode,IEnumerable) with RefreshMode.StoreWins and your deleted entities as parameter.
try{
Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();
}
catch(DbUpdateConcurrencyException)
{
IObjectContextAdapter adapter = Db;
adapter.ObjectContext.Refresh(RefreshMode.StoreWins, context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Deleted));
Db.SaveChanges();
}
Based on the code from https://msdn.microsoft.com/en-US/data/jj592904 but where I added an infite loop counter (just in case, you never know, right?) and looping through all the entries in the exception's list.
var maxTriesCounter = 20;
bool saveFailed;
do
{
saveFailed = false;
maxTriesCounter--;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
foreach (var entry in ex.Entries)
{
entry.Reload();
}
}
} while (saveFailed && maxTriesCounter > 0);
Here is what I use. Detach all problem records after the save.
Db.Entry(itemToRemove).State = EntityState.Deleted;
while(true)
try {
Db.SaveChanges();
break;
} catch (DbUpdateConcurrencyException ex) {
ex.Entries.ToList().ForEach(x=>x.State=EntityState.Detached);
}
Or you could add a custom SaveChanges function to your DbContext class and use it instead whenever you need to ignore those errors.
public int SaveChanges_IgnoreConcurrencyExceptions () {
while(true)
try {
return this.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
ex.Entries.ToList().ForEach(x => x.State=EntityState.Detached);
}
}
This is my approach:
public async Task DeleteItem(int id)
{
bool isDone = false;
while (!isDone)
{
var item= await dbContext.Items.AsNoTracking().SingleOrDefaultAsync(x=> x.id== id);
if (item== null)
return;
dbContext.Items.Delete(item);
try
{
await dbContext.CommitAsync();
return;
}
catch (DbUpdateConcurrencyException ex)
{
}
}
}
This is another approach:
context.Delete(item);
bool saveFailed;
do
{
saveFailed = false;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
var entity = ex.Entries.Single();
await entity.Single().ReloadAsync();
if (entity.State == EntityState.Unchanged)// entity is already updated
context.Delete(item);;
else if (entity.State == EntityState.Detached) // entity is already deleted
saveFailed =false;
}
} while (saveFailed);
ReloadAsync() method as of Microsoft docs :
Reloads the entity from the database overwriting any property values
with values from the database.
The entity will be in the Unchanged state after calling this method,
unless the entity does not exist in the database, in which case the
entity will be Detached. Finally, calling Reload on an Added entity
that does not exist in the database is a no-op. Note, however, that an
Added entity may not yet have had its permanent key value created.

Write to Event Log - The source was not found, but some or all event logs could not be searched. Inaccessible logs: Security."

I'am trying to write some messages to Windows Event log.
The (security) exception will be thrown when calling function "SourceExists()".
private bool CheckIfEventLogSourceExits()
{
try
{
if (!EventLog.SourceExists(this.BaseEventLog))
{
return false;
}
return true;
}
catch (System.Security.SecurityException)
{
return false;
}
}
All answers to this question are explaining how you can MANUALLY resolve issue.
Like here: Stackoverflow Thread. Solution would be changing some registry keys
But you can't expect that everyone who consumes your application is aware of these changes.
So my question is, how can we solve this issue programmatically?
Below my code:
try
{
string sLog = "Application";
if (CheckIfEventLogSourceExits())
{
EventLog.CreateEventSource(this.BaseEventLog, sLog);
}
EventLog.WriteEntry(this.BaseEventLog, message, eventLogEntryType);
}
catch (Exception ex)
{
ex.Source = "WriteToEventLog";
throw ex;
}
I know it's too late for this posting, but the answer, I found from similar experience, is that the service you're running under doesn't have administrative rights to the machine and, thus, can't write to the logs.
It's easy enough to figure out if an app is being run under admin rights. You can add something like this to your code with a message box advising the user to run "admin".
private void GetServicePermissionLevel()
{
bool bAdmin = false;
try {
SecurityIdentifier sidAdmin = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
AppDomain myDomain = Thread.GetDomain();
myDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal myPrincipal = (WindowsPrincipal)Thread.CurrentPrincipal;
if (myPrincipal.IsInRole(sidAdmin)) {
bAdmin = true;
} else {
bAdmin = false;
}
} catch (Exception ex) {
throw new Exception("Error in GetServicePermissionlevel(): " + ex.Message + " - " + ex.StackTrace);
} finally {
_ServiceRunAsAdmin = bAdmin;
}
}

how to write error message to DB with EF when SaveChanges?

With EF, any data value in Entity instance can be saved back to database by calling SaveChanges which can be overridden to add custom action.
So I try to override SaveChanges in following way:
public override int SaveChanges(System.Data.Objects.SaveOptions options)
{
try
{
return base.SaveChanges(options);
}
catch (Exception ex)
{
//error log here
//write the error message to database table errorlog
throw ex;
}
}
when SaveChange failed, I want to grab the exception and save the error message to a table in same database. With about code, even save data to table errorlog, also should call SaveChanges. How to resolve this problem?
Initialize a new context instance in your catch block and log the error.
public override int SaveChanges(System.Data.Objects.SaveOptions options)
{
try
{
return base.SaveChanges(options);
}
catch (Exception ex)
{
var context = new MyContext();
// your log class instance populated with relevant details
var error = new Error { Message = ex.Message; };
context.Errors.AddObject(error);
context.SaveChanges();
throw;
}
}
In addition to #Eranga's advice use separate context type just for error logging (it will map only Error entity). This new context type will not have overriden SaveChanges - that will avoid infinite loop in case of error fired during error saving.