Revert DbContext.Savechanges in case a second DbContext.Savechanges fail - entity-framework

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();
}
}
}

Related

Entity Framework Core : The database transaction is not always rolled back on errors Entity Framework

An action item from the security scan is to address the following error
The database transaction is not always rolled back on errors entity framework in DatabaseContext.cs
Open database connection with try {} block
Begin database transaction with try {} block
No unfinished transaction before closing database
Ensure the transaction is rolled back in the catch { } block or finally { } block
The method that causes this issue is
public async Task CommitTransactionAsync(IDbContextTransaction transaction)
{
if (transaction == null)
{
throw new ArgumentNullException(nameof(transaction));
}
try
{
await this.SaveChangesAsync().ConfigureAwait(false);
transaction.Commit();
}
catch
{
this.RollbackTransaction();
throw;
}
finally
{
if (this.currentTransaction != null)
{
this.currentTransaction.Dispose();
this.currentTransaction = null;
}
}
}
As far as I see, Catch block handles the roll back in case of any errors. Am I missing anything here?
Update:
Below is BeginTransaction method
public async Task<IDbContextTransaction> BeginTransactionAsync()
{
if (this.currentTransaction != null)
{
return null;
}
this.currentTransaction = await this.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted).ConfigureAwait(false);
return this.currentTransaction;
}
and it is used like
using (var transaction = await this.demoRepository.UnitOfWork.BeginTransactionAsync().ConfigureAwait(false))
{
await this.demoRepository.UnitOfWork.CommitTransactionAsync(transaction).ConfigureAwait(false);
}

EF6 using transaction in inner methods

I've been seen many posts about using ef6 transactions but all the SaveChanges() are in the same block.
What I want is to use a transaction and call multiple functions inside a block, each one having SaveChanges() but belonging to the main transaction block.
I already tried code like the following:
using(var transaction = context.Database.BeginTransaction())
{
try
{
doSomething(); //Has SaveChanges() and also sub functions with also SaveChanges()
doSomethingElse(); //Same as before
}
catch (Exception exp)
{
transaction.Rollback();
}
transaction.Commit();
}
What happens is that transaction.Rollback() does nothing at all.
I assume that the inner functions have their own transaction scope and don't care about this one. So how can I put this to work?
I did a quick check in LinqPad:
void Main()
{
using (var transaction = Database.BeginTransaction())
{
var z = z_pdd_log.First(p => p.id == 100001);
Console.WriteLine(z.result);
z.result = "TEST";
this.SaveChanges();
Console.WriteLine(z.result);
transaction.Rollback();
DetachAll();
z = z_pdd_log.First(p => p.id == 100001);
Console.WriteLine(z.result);
}
}
public void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = System.Data.Entity.EntityState.Detached;
}
}
}
which results in:
OK
TEST
OK
The rollback works.
Maybe your doSomthing-Methods did not throw an exception so the rollback never happened. Could you please check?

Transaction in Entity Framwork- save the record and rollback which fails

I have written 3 statements within a transation:
first for insert
second for update
third for delete
If first and second execute succesfully, and the third one fails due to some error, then I want to rollback the only third statement, and the first two should be saved.
Is it possible?
using (var context = new YourContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
//first for insert
//second for update
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback();
}
}
using (var dbContextTransaction1 = context.Database.BeginTransaction())
{
try
{
//third for delete
dbContextTransaction1.Commit();
}
catch (Exception)
{
dbContextTransaction1.Rollback();
}
}
}
}

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.

How to get EF Code First DbContext Transaction row identity?

Is it possible to get the identity of a record after the SaveChanges method but prior to the TransactionScope.Complete call using the DbContext? Do we need to cast to ObjectContext to get this functionality?
We need this when we send a message to a transactional queue on a separate server using MSDTC.
Example:
static void Main(string[] args)
{
const string qName = ".\\Private$\\DbContextTransactions";
var db = new BlogDb();
// force db creation & create queue
Console.WriteLine("Count={0}", db.Posts.Count());
if (!MessageQueue.Exists(qName))
MessageQueue.Create(qName, true); // transactional
try
{
using (var t = new TransactionScope())
using (MessageQueue q = new MessageQueue(qName))
{
var post = new Post() { Title = "Test " + DateTime.Now.Ticks.ToString() };
db.Posts.Add(post);
db.SaveChanges();
// TODO: how do we get post.Id (updated identity)?
q.Send(post.Id, MessageQueueTransactionType.Automatic);
t.Complete();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Count={0}", db.Posts.Count());
Console.ReadLine();
}
The identity value should always be set after you call SaveChanges, even inside a TransactionScope.